并发编程—队列,生产者消费者模型
1.队列
进程彼此之间互相隔离,要实现进程间通信(IPC),multiprocessing模块支持两种形式:队列和管道,这两种方式都是使用消息传递的。
1.1 队列(Queue)
底层以管道和锁定的形式实现
1.2 Queue的方法
-
Queue([maxsize])
创建共享的进程队列
maxsize:队列中允许的最大项数,如果省略,则无大小限制。
from multiprocessing import Queue q = Queue(5) #队列最大项数5
-
q.put(obj, block=True, [timeout])
from multiprocessing import Queue q = Queue(1) # 将obj放入队列,如果队列已满,将阻塞至有可用空间为止 q.put('a') # block控制阻塞行为,block默认为True,如果设置为False且Queue已满,则引发Queue.Full异常 q.put('b', block=False) q.put_nowait('e') # 等同于q.put('b', block=False) # timeout控制阻塞时间,block为True,在计时结束后,如果Queue还是满的,则引发Queue.Full异常 q.put('c', block=True, timeout=2) q.put('d', timeout=2)
-
q.get(block=True, [timeout])
from multiprocessing import Queue q = Queue() # 从队列中弹出一个元素,如果队列已空,将阻塞至可以获取到元素为止 q.get() # block控制阻塞行为,block默认为True,如果设置为False且Queue已空,则引发Queue.Empty异常 q.get(block=False) q.get_nowait() # 等同于q.get(block=False) # timeout控制阻塞时间,block为True,在计时结束后,如果Queue还是空的,则引发Queue.Empty异常 q.get(block=True, timeout=2) q.get(timeout=2)
2. 生产者消费者模型
2.1 什么是生产者消费者模型
生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。
2.2 为什么使用生产者消费者模型
在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题于是引入了生产者和消费者模式。
2.3 生产者消费者模型作用
在并发编程中使用生产者和消费者模式能够解决绝大多数并发问题。该模式通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度。
# 生产者消费者模型总结
# 程序中有两类角色
一类负责生产数据(生产者)
一类负责处理数据(消费者)
# 引入生产者消费者模型为了解决的问题是:
平衡生产者与消费者之间的工作能力,从而提高程序整体处理数据的速度
# 如何实现:
生产者<-->队列<——>消费者
# 生产者消费者模型实现类程序的解耦和
2.4 使用队列实现生产者消费者模型
import time, random
from multiprocessing import Process, Queue
def producer(q):
"""生产者"""
for i in range(10):
time.sleep(random.randint(1, 3))
q.put(i)
print(f'生产{i}')
def consumer(q):
"""消费者"""
while True:
time.sleep(random.randint(0, 5))
res = q.get()
print(f'消费{res}')
if __name__ == '__main__':
q = Queue()
p = Process(target=producer, args=(q,))
p.start()
c = Process(target=consumer, args=(q,))
c.start()
此时的问题是主进程永远不会结束,原因是:生产者p在生产完后就结束了,但是消费者c在取空了q之后,则一直处于死循环中且卡在q.get()这一步。
解决方式无非是让生产者在生产完毕后,往队列中再发一个结束信号,这样消费者在接收到结束信号后就可以break出死循环。
解决方法一:生产者在结束后向队列添加标记None,消费者获取到None就break掉while循环
import time, random
from multiprocessing import Process, Queue
def producer(q, name):
"""生产者"""
for i in range(10):
time.sleep(random.randint(1, 3))
res = f'{name}生产{i}'
q.put(res)
print(res)
q.put(None)
def consumer(q, name):
"""消费者"""
while True:
time.sleep(random.randint(0, 5))
if not res:
break
res = q.get()
print(f'{name}消费{res}')
if __name__ == '__main__':
q = Queue()
p1 = Process(target=producer, args=(q, 'p1'))
p2 = Process(target=producer, args=(q, 'p2'))
p1.start()
p2.start()
c = Process(target=consumer, args=(q, 'c'))
c.start()
问题:多个消费者不是在一个时刻结束的,队列中掺杂多个None,如果有个消费者获取到None,这个消费者就结束了,但队列中还有元素,不符合要求。
解决方法二:主进程在生产者结束后向队列中添加标记None
import time, random
from multiprocessing import Process, Queue
def producer(q, name):
"""生产者"""
for i in range(10):
time.sleep(random.randint(1, 3))
res = f'{name}生产{i}'
q.put(res)
print(res)
def consumer(q, name):
"""消费者"""
while True:
time.sleep(random.randint(0, 5))
res = q.get()
if not res:
break
print(f'{name}消费{res}')
if __name__ == '__main__':
q = Queue()
p1 = Process(target=producer, args=(q, 'p1'))
p2 = Process(target=producer, args=(q, 'p2'))
p1.start()
p2.start()
c = Process(target=consumer, args=(q, 'c'))
c.start()
p1.join()
p2.join()
q.put(None)
问题:有多少生产者就得join多少次,有多少个消费者就得添加多少个None
3. 使用JoinableQueue实现生产者消费者模型
3.1 JoinabaleQueue
-
JoinabaleQueue([maxsize])
maxsize为队列中允许的最大项数,如果省略,则无大小限制。
3.2 JoinabaleQueue的方法
-
q.task_done()
每次get后,发出信号,get任务已结束,如果调用此方法的次数大于从队列中删除项目的数量,将引发ValueError异常
-
q.join()
阻塞,等待队列中所有的任务完成
3.3 JoinabaleQueue用法
from multiprocessing import JoinableQueue
q = JoinableQueue()
q.put('a') # 往队列里放入了一个任务
q.put('b')
q.get()
q.task_done() # 完成了一个任务
q.get()
# q.join # 如果放在这里,任务数并没有降为0,将会一直阻塞
q.task_done()
q.join() # 阻塞,等待所有任务完成
3.4 使用JoinableQueue实现生产者消费者模型
import time, random
from multiprocessing import Process, JoinableQueue
def producer(q, name):
"""生产者"""
for i in range(10):
time.sleep(random.randint(0, 2))
res = f'{name}生产{i}'
q.put(res)
print(res)
def consumer(q, name):
"""消费者"""
while True:
time.sleep(random.randint(0, 2))
res = q.get()
q.task_done() # 一个任务已完成,任务数减一
print(f'{name}消费{res}')
if __name__ == '__main__':
q = JoinableQueue()
p1 = Process(target=producer, args=(q, 'p1'))
p2 = Process(target=producer, args=(q, 'p2'))
p1.start()
p2.start()
c = Process(target=consumer, args=(q, 'c'))
c.daemon = True # 将消费者设置为守护进程
c.start()
p1.join() # 确保所有的生产者任务都执行,即任务都加入了队列
p2.join()
q,join() # 如果队列里所有任务都被执行,则阻塞解除,主进程结束,守护进程结束