day034 锁,信号量,事件,队列,子进程与子进程通信,生产者消费者模型,joinableQueue
进程锁
#加锁可以保证多个进程修改同一块数据时,同一时间只能有一个任务可以进行修改,即串行的修改,没错,速度是慢了,但牺牲了速度却保证了数据安全。
虽然可以用文件共享数据实现进程间通信,但问题是:
1.效率低(共享数据基于文件,而文件是硬盘上的数据)
2.需要自己加锁处理
异步运行到某段程序时转换成同步:降低了效率但是确保了数据的安全性(串行),引入multiprocessing中的Lock:
from multiprocessing import Process,Lock
import time import json import random def get_ticket(i,ticket_lock): print("大家都到齐了") ticket_lock.acquire() #抢钥匙 以下变成串行 with open("ticket","r") as f: last_ticket_info=json.load(f) last_ticket=last_ticket_info["count"] if last_ticket>0: time.sleep(random.random()) last_ticket=last_ticket-1 last_ticket_info["count"]=last_ticket with open("ticket","w") as f: json.dump(last_ticket_info,f) print("%s号抢到了" % i) else: print("%s号没抢到了" % i) ticket_lock.release() #交钥匙给下一个人 if __name__=="__main__": ticket_lock=Lock() #创建锁得的对象 for i in range(10): p=Process(target=get_ticket,args=(i,ticket_lock)) # 传参 p.start()
#因此我们最好找寻一种解决方案能够兼顾:1、效率高(多个进程共享一块内存的数据)2、帮我们处理好锁问题。这就是mutiprocessing模块为我们提供的基于消息的IPC通信机制:队列和管道。
队列和管道都是将数据存放于内存中
队列又是基于(管道+锁)实现的,可以让我们从复杂的锁问题中解脱出来,
我们应该尽量避免使用共享数据,尽可能使用消息传递和队列,避免处理复杂的同步和锁问题,而且在进程数目增多时,往往可以获得更好的可获展性。
队列
进程彼此之间互相隔离,要实现进程间通信(IPC),multiprocessing模块支持两种形式:队列和管道,这两种方式都是使用消息传递的。队列就像一个特殊的列表,但是可以设置固定长度,并且从前面插入数据,从后面取出数据,先进先出。
from multiprocessing import Queue q=Queue(3) #创建一个队列对象,队列长度为3 #put ,get ,put_nowait,get_nowait,full,empty q.put(3) #往队列中添加数据 q.put(2) q.put(1) # q.put(4) # 如果队列已经满了,程序就会停在这里,等待数据被别人取走,再将数据放入队列。 # 如果队列中的数据一直不被取走,程序就会永远停在这里。 try: q.put_nowait(4) # 可以使用put_nowait,如果队列满了不会阻塞,但是会因为队列满了而报错。 except: # 因此我们可以用一个try语句来处理这个错误。这样程序不会一直阻塞下去,但是会丢掉这个消息。 print('队列已经满了') # 因此,我们再放入数据之前,可以先看一下队列的状态,如果已经满了,就不继续put了。 print(q.full()) #查看是否满了,满了返回True,不满返回False print(q.get()) #取出数据 print(q.get()) print(q.get()) # print(q.get()) # 同put方法一样,如果队列已经空了,那么继续取就会出现阻塞。 try: q.get_nowait(3) # 可以使用get_nowait,如果队列满了不会阻塞,但是会因为没取到值而报错。 except: # 因此我们可以用一个try语句来处理这个错误。这样程序不会一直阻塞下去。 print('队列已经空了') print(q.empty()) #空了 队列的简单用法
队列是进程安全的:同一时间只能一个进程拿到队列中的一个数据,你拿到了一个数据,这个数据别人就拿不到了。
生产者和消费者模型:
import time from multiprocessing import Process,Queue def produce(q): for i in range(1,11): time.sleep(1) print("生产了%s包子"% i) q.put(i) def consumer(q): while 1: time.sleep(2) s=q.get() if s==None: #判断是否还有数据(接收完成信号) break else: print("消费者吃了%s包子" % s) if __name__=="__main__": q= Queue(20) #设置队列长度 pro_p=Process(target=produce,args=(q,)) #创建进程 pro_p.start() con_p=Process(target=consumer,args=(q,)) #创建进程 con_p.start() pro_p.join() q.put(None) #在主程序中加信号也可以在生产者中添加完成信号
利用JoinableQueue来解决:
maxsize是队列中允许最大项数,省略则无大小限制。
方法介绍:
JoinableQueue的实例p除了与Queue对象相同的方法之外还具有:
q.task_done():使用者使用此方法发出信号,表示q.get()的返回项目已经被处理。如果调用此方法的次数大于从队列中删除项目的数量,将引发ValueError异常
q.join():生产者调用此方法进行阻塞,直到队列中所有的项目均被处理。阻塞将持续到队列中的每个项目均调用q.task_done()方法为止,也就是队列中的数据全部被get拿走了。
import time from multiprocessing import Process,JoinableQueue def produce(q): for i in range(1,11): time.sleep(1) print("生产了%s包子"% i) q.put(i) q.join() #接收完成的信号结束进程 def consumer(q): while 1: time.sleep(2) s=q.get() print("消费者吃了%s包子" % s) q.task_done()#给共享的进程发送完成的信号 if __name__=="__main__": q= JoinableQueue(20) #设置队列长度 pro_p=Process(target=produce,args=(q,)) #创建对象 pro_p.start() con_p=Process(target=consumer,args=(q,)) #创建对象 con_p.daemon=True con_p.start() pro_p.join() #等待生产者进程完成 print("主程序运行完成")
信号量
互斥锁同时只允许一个线程更改数据,而信号量Semaphore是同时允许一定数量的线程更改数据 。
import time import random from multiprocessing import Process,Semaphore def dbj(i,s): s.acquire() #进程锁 串行 print('%s号男主人公来洗脚'%i) print('-------------') time.sleep(random.randrange(3,6)) s.release() if __name__ == '__main__': s = Semaphore(4) #创建一个计数器,每次acquire就减1,直到减到0,那么上面的任务只有4个在同时异步的执行,后面的进程需要等待. for i in range(10): p1 = Process(target=dbj,args=(i,s,)) p1.start()
事件
python线程的事件用于主线程控制其他线程的执行,事件主要提供了三个方法 set、wait、clear。
事件处理的机制:全局定义了一个“Flag”,如果“Flag”值为 False,那么当程序执行 event.wait 方法时就会阻塞,如果“Flag”值为True,那么event.wait 方法时便不再阻塞。
clear:将“Flag”设置为False
set:将“Flag”设置为True
from multiprocessing import Process,Semaphore,Event import time,random e = Event() #创建一个事件对象 print(e.is_set()) #is_set()查看一个事件的状态,默认为False,可通过set方法改为True print('look here!') # e.set() #将is_set()的状态改为True。 # print(e.is_set())#is_set()查看一个事件的状态,默认为False,可通过set方法改为Tr # e.clear() #将is_set()的状态改为False # print(e.is_set())#is_set()查看一个事件的状态,默认为False,可通过set方法改为Tr e.wait() #根据is_set()的状态结果来决定是否在这阻塞住,is_set()=False那么就阻塞,is_set()=True就不阻塞 print('give me!!') #set和clear 修改事件的状态 set-->True clear-->False #is_set 用来查看一个事件的状态 #wait 依据事件的状态来决定是否阻塞 False-->阻塞 True-->不阻塞 事件方法的使用