[toc]
# **进程间同步控制**模块
锁 :互斥锁Lock 数据的安全性问题 进程之间的
信号量 : Semaphore 锁+计数器
事件 :通过一个flag来控制进程的阻塞和执行
## 锁(Lock)的用途
加锁可以保证多个进程修改同一块数据时,同一时间只能有一个任务可以进行修改,即串行地修改,没错,速度是慢了,但牺牲了速度却保证了数据安全。
### 互斥锁的原理
互斥锁的意思就是互相排斥,如果把多个进程比喻为多个人,互斥锁的工作原理就是多个人都要去争抢同一个资源:卫生间,一个人抢到卫生间后上一把锁,其他人都要等着,等到这个完成任务后释放锁,其他人才有可能有一个抢到......
**互斥锁的原理,就是把并发改成穿行,降低了效率,但保证了数据安全不错乱**
### 语法和方法
Lock属于multiprocessing模块,常用的方法如下
~~~
from multiprocessing import Process,Lock #导入
lock.acquire() #加锁
lock.release() #释放锁
~~~
### 模拟抢票练习
多个进程共享同一文件,我们可以把文件当数据库,用多个进程模拟多个人执行抢票任务
~~~
#文件db.txt的内容为:{"count":1}
from multiprocessing import Process
import time,json
def search(name):
dic=json.load(open('db.txt'))
time.sleep(1)
print('\033[43m%s 查到剩余票数%s\033[0m' %(name,dic['count']))
def get(name):
dic=json.load(open('db.txt'))
time.sleep(1) #模拟读数据的网络延迟
if dic['count'] >0:
dic['count']-=1
time.sleep(1) #模拟写数据的网络延迟
json.dump(dic,open('db.txt','w'))
print('\033[46m%s 购票成功\033[0m' %name)
def task(name):
search(name)
get(name)
if __name__ == '__main__':
for i in range(4): #模拟并发4个客户端抢票
name='<路人%s>' %i
p=Process(target=task,args=(name,))
p.start()
~~~
并发运行,效率高,但竞争写同一文件,数据写入错乱,只有一张票,卖成功给了4个人
~~~
<路人0> 查到剩余票数1
<路人1> 查到剩余票数1
<路人2> 查到剩余票数1
<路人3> 查到剩余票数1
<路人0> 购票成功
<路人1> 购票成功
<路人7> 购票成功
<路人2> 购票成功
~~~
加锁处理:购票行为由并发变成了串行,牺牲了运行效率,但保证了数据安全
~~~
#把文件db.txt的内容重置为:{"count":1}
from multiprocessing import Process,Lock
import time,json
def search(name):
dic=json.load(open('db.txt'))
time.sleep(1)
print('\033[43m%s 查到剩余票数%s\033[0m' %(name,dic['count']))
def get(name):
dic=json.load(open('db.txt'))
time.sleep(1) #模拟读数据的网络延迟
if dic['count'] >0:
dic['count']-=1
time.sleep(1) #模拟写数据的网络延迟
json.dump(dic,open('db.txt','w'))
print('\033[46m%s 购票成功\033[0m' %name)
def task(name,lock):
search(name)
with lock: #相当于lock.acquire(),执行完自代码块自动执行lock.release()
get(name)
if __name__ == '__main__':
lock=Lock()
for i in range(4): #模拟并发4个客户端抢票
name='<路人%s>' %i
p=Process(target=task,args=(name,lock))
p.start()
~~~
执行结果
~~~
<路人0> 查到剩余票数1
<路人1> 查到剩余票数1
<路人2> 查到剩余票数1
<路人3> 查到剩余票数1
<路人0> 购票成功
~~~
### 互斥锁与join
使用join可以将并发变成串行,互斥锁的原理也是将并发变成穿行,那我们直接使用join就可以了啊,为何还要互斥锁.
join是将一个任务整体串行,而互斥锁的好处则是可以将一个任务中的某一段代码串行,比如只让task函数中的get任务串行
~~~
def task(name,):
search(name) # 并发执行
lock.acquire()
get(name) #串行执行
lock.release()
~~~
## 信号量(Semaphore)
信号量是对锁的封装,锁是同一时间只能有一个进程可以使用,信号量是同一个之间可以有指定数量的进程可以使用,简单理解就是锁+计数器实现的
~~~
Semaphore管理一个内置的计数器,
每当调用acquire()时内置计数器-1;
调用release() 时内置计数器+1;
计数器不能小于0;当计数器为0时,acquire()将阻塞线程直到其他线程调用release()。
~~~
### 信号量的使用
信号量的使用和锁基本完全一样,因此直接用下面的争抢KTV的代码来演示
~~~
from multiprocessing import Process
from multiprocessing import Semaphore
import time,random
def ktv(i,sm):
sm.acquire()
print('%s来了KTV'%i)
time.sleep(random.randint(1,3))
print('>>%s唱完了'%i)
sm.release()
if __name__=='__main__':
sm=Semaphore(2)
l=[]
for i in range(4):
p=Process(target=ktv,args=(i,sm))
l.append(p)
for p in l:
p.start()
print('end......')
~~~
执行结果
```
0来了KTV
1来了KTV
>>0唱完了
2来了KTV
>>1唱完了
3来了KTV
>>2唱完了
>>3唱完了
```
## 事件(Event)
默认进程之间是不能直接通向的,而如果程序中的其他进程程需要通过判断某个进程的状态来确定自己下一步的操作,就需要使用threading库中的Event(事件)对象。
事件对象包含一个可设置的信号标志,它允许进程等待某些事件的发生。
初始情况下,Event对象中的信号标志被设置为假。如果有线程等待一个Event对象, 而这个Event对象的标志为假,那么这个线程将会被一直阻塞直至该标志为真。一个线程如果将一个Event对象的信号标志设置为真,它将唤醒所有等待这个Event对象的线程。如果一个线程等待一个已经被设置为真的Event对象,那么它将忽略这个事件, 继续执行
~~~
from threading import Event
event.isSet():返回event的状态值;
event.wait():如果 event.isSet()==False将阻塞线程;
event.set(): 设置event的状态值为True
#所有阻塞池的线程激活进入就绪状态, 等待操作系统调度;
event.clear():恢复event的状态值为False。
~~~
### 红绿灯模型演示
红绿灯是一个典型的需要用到事件的模型,红灯和绿灯需要交替的闪烁,而汽车只有当绿灯时才能通过,如果遇到红灯,需要等待红灯变为绿灯
~~~
from multiprocessing import Process
from multiprocessing import Event
import time,random
def traffic_ligths(ev):
print('当前是红灯', ev.is_set())
while True:
time.sleep(2)
if ev.is_set():
print('绿灯-->红灯')
ev.clear()
else:
print('红灯-->绿灯')
ev.set()
def car(ev,i):
if not ev.is_set():
print('卡车%s准备通过'%i)
ev.wait()
print('卡车%s通过'%i)
if __name__=='__main__':
ev=Event()
p1=Process(target=traffic_ligths,args=(ev,))
p1.daemon
p1.start()
for i in range(5):
p2=Process(target=car,args=(ev,i))
p2.start()
time.sleep(random.randrange(0,5))
~~~
执行结果:
```
当前是红灯 False
卡车0准备通过
红灯-->绿灯
卡车0通过
绿灯-->红灯
卡车1准备通过
卡车2准备通过
红灯-->绿灯
卡车2通过
卡车1通过
卡车3通过
卡车4通过
绿灯-->红灯
```
- 基础部分
- 基础知识
- 变量
- 数据类型
- 数字与布尔详解
- 列表详解list
- 字符串详解str
- 元组详解tup
- 字典详解dict
- 集合详解set
- 运算符
- 流程控制与循环
- 字符编码
- 编的小程序
- 三级菜单
- 斐波那契数列
- 汉诺塔
- 文件操作
- 函数相关
- 函数基础知识
- 函数进阶知识
- lambda与map-filter-reduce
- 装饰器知识
- 生成器和迭代器
- 琢磨的小技巧
- 通过operator函数将字符串转换回运算符
- 目录规范
- 异常处理
- 常用模块
- 模块和包相关概念
- 绝对导入&相对导入
- pip使用第三方源
- time&datetime模块
- random随机数模块
- os 系统交互模块
- sys系统模块
- shutil复制&打包模块
- json&pickle&shelve模块
- xml序列化模块
- configparser配置模块
- hashlib哈希模块
- subprocess命令模块
- 日志logging模块基础
- 日志logging模块进阶
- 日志重复输出问题
- re正则表达式模块
- struct字节处理模块
- abc抽象类与多态模块
- requests与urllib网络访问模块
- 参数控制模块1-optparse-过时
- 参数控制模块2-argparse
- pymysql数据库模块
- requests网络请求模块
- 面向对象
- 面向对象相关概念
- 类与对象基础操作
- 继承-派生和组合
- 抽象类与接口
- 多态与鸭子类型
- 封装-隐藏与扩展性
- 绑定方法与非绑定方法
- 反射-字符串映射属性
- 类相关内置方法
- 元类自定义及单例模式
- 面向对象的软件开发
- 网络-并发编程
- 网络编程SOCKET
- socket简介和入门
- socket代码实例
- 粘包及粘包解决办法
- 基于UDP协议的socket
- 文件传输程序实战
- socketserver并发模块
- 多进程multiprocessing模块
- 进程理论知识
- 多进程与守护进程
- 锁-信号量-事件
- 队列与生产消费模型
- 进程池Pool
- 多线程threading模块
- 进程理论和GIL锁
- 死锁与递归锁
- 多线程与守护线程
- 定时器-条件-队列
- 线程池与进程池(新方法)
- 协程与IO模型
- 协程理论知识
- gevent与greenlet模块
- 5种网络IO模型
- 非阻塞与多路复用IO实现
- 带着目标学python
- Pycharm基本使用
- 爬虫
- 案例-爬mzitu美女
- 案例-爬小说
- beautifulsoup解析模块
- etree中的xpath解析模块
- 反爬对抗-普通验证码
- 反爬对抗-session登录
- 反爬对抗-代理池
- 爬虫技巧-线程池
- 爬虫对抗-图片懒加载
- selenium浏览器模拟