第一步 导入事件模块
from multiprocessing import Event
第二步 定义事件对象,测试操作方法
e = Event()
e.set() # 将 is_set()的bool值改为 True
e.clear() # 将 is_set()的bool值改为 False
e.wait() # 根据 is_set()的bool值去改变 阻塞/非阻塞 状态
e.is_set() # 返回一个bool值,去标识e.wait()的阻塞状态,当其为 False 时,e.wait()是非阻塞状态,反之亦反
from multiprocessing import Process, Lock, Semaphore, Event
import time
def traffic_light(e):
while 1 :
if e.is_set(): # 代表现在状态true,绿灯车辆可以通行
time.sleep(5)
# 休眠5秒钟之后,需要将绿灯变为红灯,将状态转变列false
print("\033[31m红灯亮,终止通行\033[0m")
e.clear() # 将状态改为false
else: # 代表现在状态为false,现在是红灯,车辆等待
time.sleep(5)
print("\033[32m绿灯亮,可以通行\033[0m")
e.set() # 将状态改为true
def car(e,i):
e.wait()
print(f"绿灯亮起,第{i}辆车开始通行...")
if __name__ == '__main__':
e = Event()
# 启动红绿灯进程
tl = Process(target=traffic_light,args=(e,))
tl.start()
for i in range(20):
time.sleep(1)
car0 = Process(target=car,args=(e,i+1))
car0.start()
multiprocessing.Queue
导入队列包
from multiprocessing import Queue # 队列
创建队列
q = Queue([maxsize]) # maxsize 创建共享队列的最大项数
使用队列方法
q.put("肉松")
q.put("蛋炒面")
q.put("蛋炒饭")
q.put("西红柿炒鸡蛋")
q.put("馒头")
q.get()
q.put("饺子")
print("Over")
队列常用方法
# put 向队列中添加元素,如果添加数量超出队列长度,那么程序阻塞,等待get方法执行
# get 从队列中提取元素,如果获取数量超出队列长度,那么程序阻塞,等待put方法执行
# put_nowait 队列中添加元素,如果添加数量超出队列长度,那么程序报错 queue.Full
# get_nowait 队列中提取元素,如果获取数量超出队列长度,那么程序报错 queue.Empty
# q.qsize() 返回队列中目前项目的正确数量。此函数的结果并不可靠
# q.empty() 如果调用此方法时 q为空,返回True。如果其他进程或线程正在往队列中添加项目,结果是不可靠的。
# q.full() 如果q已满,返回为True. 由于线程的存在,结果也可能是不可靠的
在并发编程中使用生产者和消费者模式能够解决绝大多数并发问题。该模式通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度。生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。
为什么要使用生产者和消费者模式
在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题于是引入了生产者和消费者模式。
from multiprocessing import Process,Queue
import time,random,os
def consumer(q):
while True:
res=q.get()
time.sleep(random.randint(1,3))
print('\033[45m%s 吃 %s\033[0m' %(os.getpid(),res))
def producer(q):
for i in range(10):
time.sleep(random.randint(1,3))
res='包子%s' %i
q.put(res)
print('\033[44m%s 生产了 %s\033[0m' %(os.getpid(),res))
if __name__ == '__main__':
q=Queue()
#生产者们:即厨师们
p1=Process(target=producer,args=(q,))
#消费者们:即吃货们
c1=Process(target=consumer,args=(q,))
#开始23
p1.start()
c1.start()
print('主')
此时的问题是主进程永远不会结束,原因是:生产者p在生产完后就结束了,但是消费者c在取空了q之 后,则一直处于死循环中且卡在q.get()这一步。 解决方式无非是让生产者在生产完毕后,往队列中再发一个结束信号,这样消费者在接收到结束信号 后就可以break出死循环。
q.put(None) #有几个消费者就应该发送几次结束信号None 37
q.put(None) #发送结束信号
创建可连接的共享进程队列。这就像是一个Queue对象,但队列允许项目的使用者通知生产者项目已 经被成功处理。通知进程是使用共享的信号和条件变量来实现的。
创建队列
from multiprocessing import JoinableQueue
q=JoinableQueue([maxsize])
常用方法
q.task_done()
# 使用者使用此方法发出信号,表示q.get()返回的项目已经被处理。如果调用此方法的次数大于从队列中删除的项目数量,将引发ValueError异常。用于消费者,是指每消费队列中的一个数据,就给join返回一个标识
q.join()
# 生产者将使用此方法进行阻塞,直到队列中所有项目均被处理。阻塞将持续到为队列中的每个项目均调用q.task_done()方法为止。
示例
from multiprocessing import Process,JoinableQueue
import time,random,os
def consumer(q):
while True:
res=q.get()
time.sleep(random.randint(1,3))
print('\033[45m%s 吃 %s\033[0m' %(os.getpid(),res))
q.task_done() #向q.join()发送一次信号,证明一个数据已经被取走了
def producer(name,q):
for i in range(10):
time.sleep(random.randint(1,3))
res='%s%s' %(name,i)
q.put(res)
print('\033[44m%s 生产了 %s\033[0m' %(os.getpid(),res))
q.join() #生产完毕,使用此方法进行阻塞,直到队列中所有项目均被处理。
if __name__ == '__main__':
q=JoinableQueue()
#生产者们:即厨师们
p1=Process(target=producer,args=('rose',q))
p2=Process(target=producer,args=('波姐',q))
p3=Process(target=producer,args=('老王',q))
#消费者们:即吃货们
c1=Process(target=consumer,args=(q,))
c2=Process(target=consumer,args=(q,))
c1.daemon=True
c2.daemon=True
#开始
p_l=[p1,p2,p3,c1,c2]
for p in p_l:
p.start()
p1.join()
p2.join()
p3.join()
print('主')
#主进程等--->p1,p2,p3等---->c1,c2 41
#p1,p2,p3结束了,证明c1,c2肯定全都收完了p1,p2,p3发到队列的数据42
#因而c1,c2也没有存在的价值了,不需要继续阻塞在进程中影响主进程了。应该随着主进程的结束而结束,所以设置成守护进程就可以了。
方法一 访问文件方式共享
访问同一个文件中的数据
方法二 multiprocessing.Value包
from multiprocessing import Value
money = Value("i",<数值>)
# 使用
money.value += 1
示例
见上课笔记4
方法三 导入multiprocessing.Manager包
from multiprocessing import Manager
m = Manager()
m.list/dict/... = {}/[]/...
# 使用
m['count']-=1
示例
from multiprocessing import Manager,Process,Lock
def work(d,lock):
with lock: #不加锁而操作共享的数据,肯定会出现数据错乱
d['count']-=1
if __name__ == '__main__':
lock=Lock()
with Manager() as m: # 往Manager对像中存放数据
dic=m.dict({'count':100})
p_l=[]
for i in range(100):
p=Process(target=work,args=(dic,lock))
p_l.append(p)
p.start()
for p in p_l:
p.join()
print(dic)
方法四 使用数据库共享数据
后面再讲
但进程间应该尽量避免通信,即便需要通信,也应该选择进程安全的工具来避免加锁带来的问题。 以后我们会尝试使用数据库来解决现在进程之间的数 据共享问题。
multiprocess.Pool
进程池的概念
定义一个池子,在里面放上固定数量的进程,有需求来了,就拿一个池中的进程来处理任务,等到处理完毕,进程并不关闭,而是将进程再放回进程池中继续等待任务。如果有很多任务需要执行,池中的进程数量不够,任务就要等待之前的进程执行任务完毕归来,拿到空闲进程才能继续执行。
from multiprocess import Pool
Pool([numprocess [,initializer [, initargs]]]) # 创建进程池
参数
1 numprocess: 要创建的进程数,如果省略,将默认使用cpu_count()的值
2 initializer:是每个工作进程启动时要执行的可调用对象,默认为None
3 initargs: 是要传给initializer的参数组
常用方法
p.map(fun[,list/dict])
方法接受两个参数,第一个参数是一个函数,第二个参数是一个列表或元组,包含所有要处理的数据 返回一个列表,包含所有进程处理完任务后的结果。
p.apply(fun[,list/dict])
方法接受两个参数,第一个参数是一个函数,第二个参数是一个列表或元组,包含所有要处理的数据 未完成。。。
p.close()
关闭进程池,防止进一步操作。如果所有操作持续挂起,它们将在工作进程终止前完成p.jion()
等待所有工作进程退出。此方法只能在close()或teminate()之后调用同步与异步
进程池同步调用
import os,time
from multiprocessing import Pool
def work(n):
print('%s run' %os.getpid())
time.sleep(3)
return n**2
if __name__ == '__main__':
p=Pool(3) #进程池中从无到有创建三个进程,以后一直是这三个进程在执行任务
res_l=[]
for i in range(10):
res=p.apply(work,args=(i,)) # 同步调用,直到本次任务执行完毕拿到res,等待任务执行的过程中可能阻塞也可能没阻塞
# 但不管该任务是否存在阻塞,同步调用都会在原地等着
print(res_l)
进程池异步调用
import os
import time
import random
from multiprocessing import Pool
def work(n):
print('%s run' %os.getpid())
time.sleep(random.random())
return n**2
if __name__ == '__main__':
p=Pool(3) #进程池中从无到有创建三个进程,以后一直是这三个进程在执行任务
res_l=[]
for i in range(10):
res=p.apply_async(work,args=(i,)) # 异步运行,根据进程池中有的进程数,每次最多3个子进程在异步执行
# 返回结果之后,将结果放入列表,归还进程,之后再执行新的任务
# 需要注意的是,进程池中的三个进程不会同时开启或者同时结束
# 而是执行完一个就释放一个进程,这个进程就去接收新的任务。
res_l.append(res)
# 异步apply_async用法:如果使用异步提交的任务,主进程需要使用join,等待进程池内任务都处理完,然后用get收集结果
# 否则,主进程结束,进程池可能还没来得及执行,也就跟着一起结束了
p.close()
p.join()
for res in res_l:
print(res.get()) #使用get来获取apply_aync的结果,如果是apply,则没有get方法,因为apply是同步执行,立刻获取结果,也根本无需get