python中生产者和消费者理论
1.模型理论
生产者消费者他是一个概念,(由于生产者消费者模型并不局限于某一类技术,因此,有多种实现方式)所以,代码很简单,所以这里首先要弄懂理论。
1.1 生产者消费者模型
模型指的是一种解决问题的套路。
1.2 生产者消费者模型中包含两类重要角色一类叫生产者,另一类叫消费者
生产者:将负责制造数据的称为生产者(生产数据)
消费者:接收生产者制造出的数据,来做进一步处理,该类任务被比喻成消费者(处理数据)
1.3 实现生产者消费者模型的三要素
生产者
消费者
队列(队列中存放的是一些消息)
1.4 生产者消费者模型的运作方式
生产者生产数据,放到一个共享的空间,然后消费者取走进行处理。
1.5 生产者消费者模型的实现方式
(由于生产者消费者模型并不局限于某一类技术,因此,有多种实现方式,不限于以下方式)
生产者进程 + 队列 + 消费者进程
1.6 该模型的应用场景
程序中出现明显的两类任务,一类任务负责生产数据,另一类任务是负责处理生产数据的,此时就应该考虑生产者消费者模型。(例如:爬虫)
1.7 使用生产者消费者模型的优势
实现了生产者与消费者解耦合。(简单就是说不要所有的功能全部一个模块上面,具体有什么好处大家可以自行去百度解耦的好处)
平衡了生产力和消费力,彼此不影响,生产者可以一致不停的生产,消费者可以一致不停的消费,因为二者不再是直接沟通了,而是跟队列沟通。
1.8 为什么要使用生产者和消费者
在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题于是引入了生产者和消费者模式。
生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。
2.举例
说了那么多,这里还是拿课堂上的例子来进行解释
课上的例子是一个厨师和顾客还有服务员之间的例子。
如果现在有顾客想要来买包子吃,假设现在还没有服务员,那么可以直接告诉厨师,我需要一个包子,那么厨师就做一个包子给顾客,这么看来毫无问题,那么这里就延伸出来几个问题。
1.如果有几个顾客都需要包子,那厨师忙不过来怎么办?厨师可能只能服务一个顾客。
2.如果同样的顾客想吃第二个包子,那怎么办?如果多个顾客都想要第二个包子怎么办?
3.比如只有一个厨师,只能招待一个顾客,其余的顾客可能觉得等的时间长了,先去干其他事情了,那么厨师不能把一个包子卖给这个去干其他事情的顾客,又卖给刚来的顾客,一个包子不能买给多个顾客。最好是先到先得。
那么有个想法看看能不能解决以上3个问题?厨师能否先做好100个包子?然后等客户来了给客户?那是当然可以,但是有2点,你不知道有多少客户,少了不够用,多了就浪费了,就阻塞了。
那么最好的解决方案就是招一个服务员,消费者去找服务员,服务员担任顾客之间的桥梁,厨师只生产包子,顾客只吃包子,至于传递要多少包子,交给服务员。那么我顾客多,我就可以多几个厨师,有了服务员记录菜单,厨师也不会把包子做多,那么效率就高起来了。
至于老师讲的join和task_done,不是那么用的,所以不要乱听视频,那么场景直接升级一下,直接调到老师课堂上没有解决的问题,就是假设1个人什么时候会去要包子吃?厨师怎么知道?就比如,有100个厨师,会不会一个人要包子,结果就是100个厨师做了1个包子?如果不断的起生产者的线程,那么就会1个顾客要吃包子,那么100个厨师都要做一个,其实这是个悖论,这老师课上没有解决,因为没有说,消费者要去吃包子,厨师才去做,而是厨师做好了,你消费者才有的吃,问题不应该是怎么解决一个人要吃包子,有100个厨师,只让一个厨师做,问题应该是,消费者怎么知道没有包子了,如果没有包子了,那么就不需要等待了。所以见如下终极代码。
上述代码限制了生产者生产的数量,但是一般程序是不会这么限制的,这里只是测试,所以无所谓,上面的代码已经是基本接近优化的了。
当然我们现在回来讨论一下老师课堂上讲解的问题,就是以下2个用法
Queue.task_done():表示前面的排队任务已经完成,被队列的消费者线程使用。每个get()被用于获取一个任务,后续调用task_done()告诉队列,该任务的处理已经完成。如果join()当前正在阻塞,在所有条目都被处理后,将解除阻塞(意味着每个put()进队列的条目task_done()都被收到)。
Queue.join():阻塞至对列的所有数据都被接收和处理完毕。当数据被添加到队列时,未完成的任务的计数就会增加。每当消费者线程调用task_done()表示这个条目已经被收回,未完成的计数就会减少,当完成计数降到0的时候,阻塞就会解除。
可以看到这2个基本上是组合使用的。可以实现,如果没有包子了,顾客会通知厨师开始做包子,但是这样感觉会阻塞队列。见如下代码:
以上的基本也完成了需求,课堂上的遗留问题就是,如果有多个厨师,一个顾客,怎么只让一个厨师做一个包子?这个问题目前没解决,还是一个顾客只要一个包子,结果2个厨师都做了?这个问题在后面的课程中老师解释,没有这样的场景,如果厨师多了,那么设计就是有问题的,只有一个顾客,却有100个厨师,本身这个问题就有问题,所以这个问题不用纠结了,所以见以下针对这个问题的代码:
3.总结
不要被举例中繁琐的例子给吓到了,那是因为那个老师想讲解的细一点,但是自己又讲解不清楚,不是第一次了,所以主要看理论就行,什么是生产证消费者?
如果要落实到代码里面,以下代码就可以代表了。