进击のpython
并发编程——生产者消费者模型
介绍这个模型,有助于更好的理解队列在真正的项目开发过程中的使用场景
方便更好的理解队列的数据处理方式
本小节针对生产者消费者模型的介绍与创建进行剖析
可以将进程的知识点进行串讲,达到一个综合的目的
生产者消费者模型
生产者:就是产生数据的任务;消费者:就是处理数据的任务
在并发的时候,如果生产者的生产速度很快,消费者处理速度很慢
那生产者就得等消费者处理完才能产生数据,同样的道理
如果消费者处理速度很快,生产者生产速度很慢
那消费者就得等生产者生产完才能够处理数据
为了解决这个问题,就引入了生产者消费者模式
生产者消费者模式
生产者消费者模式就是通过一个容器来解决生产者与消费者的强耦合问题
生产者和消费者彼此不直接通信,而是通过阻塞队列来进行通讯
所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列
消费者不找生产者要数据,而是直接从阻塞队列里取
阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力
这个阻塞队列就是用来给生产者和消费者解耦的
其实就像吃包子一样,不可能厨师做包子顾客直接吃
也不可能来顾客厨师才做包子
而正确的做法就是厨师把包子做完放在笼屉
顾客来直接从笼屉里拿包子,这样才是合情合理的
模型实现
根据队列,我们可以实现一下这个模型
import random
import time
from multiprocessing import Process, Queue
def pro(q, *args, **kwargs):
for i in range(1, 10):
time.sleep(random.random())
q.put(i)
print(f'生产出来{i}号包子.. ..')
pass
def cro(q, *args, **kwargs):
while 1:
time.sleep(random.random())
print(f'吃了{q.get()}号包子!')
pass
if __name__ == '__main__':
q = Queue()
p = Process(target=pro, args=(q,))
c = Process(target=cro, args=(q,))
p.start()
c.start()
生产出来1号包子.. ..
吃了1号包子!
生产出来2号包子.. ..
吃了2号包子!
生产出来3号包子.. ..
生产出来4号包子.. ..
生产出来5号包子.. ..
吃了3号包子!
生产出来6号包子.. ..
吃了4号包子!
生产出来7号包子.. ..
生产出来8号包子.. ..
生产出来9号包子.. ..
吃了5号包子!
吃了6号包子!
吃了7号包子!
吃了8号包子!
吃了9号包子!
这和我们想的是一样的,你吃你的,我做我的
但是运行的时候你会发现,当包子吃完了,程序处于阻塞状态,不会停止
根本原因就是,队列空了,但是while还在从队列里要值,就会导致阻塞
那这个问题怎么解决呢?我们可以给个标记,当遇到这个标记就说明吃完了
就退出程序:
import random
import time
from multiprocessing import Process, Queue
def pro(q, *args, **kwargs):
for i in range(1, 10):
time.sleep(random.random())
q.put(i)
print(f'生产出来{i}号包子.. ..')
q.put('None')
pass
def cro(q, *args, **kwargs):
while 1:
msg = q.get()
if msg == 'None': break
time.sleep(random.random())
print(f'吃了{msg}号包子!')
pass
if __name__ == '__main__':
q = Queue()
p = Process(target=pro, args=(q,))
c = Process(target=cro, args=(q,))
p.start()
c.start()
或者也可以这么写:
import random
import time
from multiprocessing import Process, Queue
def pro(q, *args, **kwargs):
for i in range(1, 10):
time.sleep(random.random())
q.put(i)
print(f'生产出来{i}号包子.. ..')
q.put('None')
pass
def cro(q, *args, **kwargs):
while 1:
msg = q.get()
if msg == 'None': break
time.sleep(random.random())
print(f'吃了{msg}号包子!')
pass
if __name__ == '__main__':
q = Queue()
p = Process(target=pro, args=(q,))
c = Process(target=cro, args=(q,))
p.start()
c.start()
p.join()
q.put("None")
但是当生产者数量变多的时候,我就需要写两个三个,更多的q.put("None")
很明显这么写是不方便的
其实我们的思路无非是发送结束信号而已,有另外一种队列提供了这种机制
JoinableQueue([maxsize])
这就像是一个Queue对象,但队列允许项目的使用者通知生成者项目已经被成功处理
通知进程是使用共享的信号和条件变量来实现的
JoinableQueue的实例p除了与Queue对象相同的方法之外还具有:
q.task_done():使用者使用此方法发出信号,表示q.get()的返回项目已经被处理
如果调用此方法的次数大于从队列中删除项目的数量,将引发ValueError异常
q.join(): 生产者调用此方法进行阻塞,直到队列中所有的项目均被处理
阻塞将持续到队列中的每个项目均调用q.task_done()方法为止
那我们的生产者消费者模型,就可以这么写:
from multiprocessing import Process, JoinableQueue
import time, random, os
def consumer(q, name):
while True:
res = q.get()
time.sleep(random.randint(1, 3))
print(' 33[43m%s 吃 %s 33[0m' % (name, res))
q.task_done() # 发送信号给q.join(),说明已经从队列中取走一个数据并处理完毕了
def producer(q, name, food):
for i in range(3):
time.sleep(random.randint(1, 3))
res = '%s%s' % (food, i)
q.put(res)
print(' 33[45m%s 生产了 %s 33[0m' % (name, res))
q.join() # 等到消费者把自己放入队列中的所有的数据都取走之后,生产者才结束
if __name__ == '__main__':
q = JoinableQueue() # 使用JoinableQueue()
# 生产者们:即厨师们
p1 = Process(target=producer, args=(q, 'ponny', '包子'))
p2 = Process(target=producer, args=(q, 'ponny', '骨头'))
p3 = Process(target=producer, args=(q, 'ponny', '泔水'))
# 消费者们:即吃货们
c1 = Process(target=consumer, args=(q, 'jevious'))
c2 = Process(target=consumer, args=(q, 'jevious'))
c1.daemon = True
c2.daemon = True
# 开始
p1.start()
p2.start()
p3.start()
c1.start()
c2.start()
p1.join()
p2.join()
p3.join()
# 1、主进程等生产者p1、p2、p3结束
# 2、而p1、p2、p3是在消费者把所有数据都取干净之后才会结束
# 3、所以一旦p1、p2、p3结束了,证明消费者也没必要存在了,应该随着主进程一块死掉,因而需要将生产者们设置成守护进程
模型总结
1、这个模型有两个角色:生产者和消费者
生产者负责生产数据,消费者负责处理数据
2、模型解决的问题是平衡这两者的速度
对这两个身份的解耦
3、怎么实现呢?生产者⇠⇢队列⇠⇢消费者