[原创]如何编写多个阻塞队列连接下的多生产者多消费者的Python程序
平常在写程序时,往往会遇到一个需求:在程序的多个阶段都会出现阻塞的可能,因此,这多个阶段就需要并发执行。
Python的多线程有一个特点,就是不允许从外部结束一个运行中的线程,这给我们编写代码时带来了一定的困难。网上现存的多篇文章,往往都从单个阻塞队列出发来讲多线程,大家都知道然而这并没有什么卵用。本文将从一个需求出发,详细分析如何编写一个没有并发问题、可以保证在正确的时间内结束的多生产者、多消费者模式下的Python程序。
言归正传,首先是需求:
我有一个已知的ip段,比如64.233.16.0/20,我需要知道这里面有几个有效的能够提供443连接的ip地址,若能够提供有效443连接,那么测算可达ip的速度。
大概框架可以如此设计:
ip段---> Ip生成器(一个线程)===(阻塞队列a)===连接测试器(多个线程)===(阻塞队列b)===速度测试器(两个线程)
在明确了需要实现的框架后,我们再来看看手头都有什么弹药。
1、守护线程。什么是守护线程?我们可以通过守护线程的行为来定义它。对于一个Python进程而言,在该进程所属的所有非守护线程结束后,如果还存在守护线程在运行,那么这些守护线程就会直接被从外部终结。
2、阻塞队列。什么是阻塞队列?阻塞队列就是库提供给我们可以在多线程环境下安全运行的容器,一般都用来实现生产者消费者模型。阻塞队列一般提供两个方法,一个是put,往队列中投放元素,若队列容量满则put的调用线程阻塞;另一个是get,往队列中取元素,若队列中无元素则get的调用线程阻塞。
对于Python的阻塞队列实现Queue而言,还有一队非常重要的方法task_done()和join()。下面我直接抄官方文档了:
Queue.task_done()
Indicate that a formerly enqueued task is complete. Used by queue consumer threads. For each get() used to fetch a task, a subsequent call to task_done() tells the queue that the processing on the task is complete.
If a join() is currently blocking, it will resume when all items have been processed (meaning that a task_done() call was received for every item that had been put() into the queue).
Raises a ValueError if called more times than there were items placed in the queue.
New in version 2.5.
Queue.join()
Blocks until all items in the queue have been gotten and processed.
The count of unfinished tasks goes up whenever an item is added to the queue. The count goes down whenever a consumer thread calls task_done() to indicate that the item was retrieved and all work on it is complete. When the count of unfinished tasks drops to zero, join() unblocks.
New in version 2.5.
可以看出,在面对生产者消费者模型时,我们先将生产者线程和消费者线程设置为守护线程,然后在主线程中用一个阻塞队列将生产者线程和消费者线程连接起来,再分别启动生产者线程和消费者线程,在消费者线程中,每消费队列中的一个元素并处理后都调用一次queue.task_done(),然后别忘了在主线程调用queue.join(),似乎就可以完美解决这个问题了。
但是,这有一个非常严重的问题:
如果生产者速度比消费者慢,那会发生什么?
由之前的描述看,如果队列中的元素被提前消耗完了,主线程就会从queue.task_done()中唤醒,那么在生产者还在生产新的元素时,就会因为主线程的退出而退出,这个时候,任务还没有结束。
而在我们这个需求中,虽然ip生成器的速度很快,不会出现这个问题,但连接测试器生产的速度远远不如速度测试器消费的速度快,立刻就会出现上述的情况,任务明明还没有结束,整个程序却得退出了。
解决方案:
1、理论
先来一个断言:在生产者生产结束后,主线程对该阻塞队列调用join方法,可以正确结束程序。
接下来是证明:不妨假设在生产者生产全部结束后(也就是待处理元素都在queue中了),主线程对阻塞队列调用join方法并没有正确结束程序。join方法取消阻塞状态的前提是队列中无待处理元素,由于没有正确结束程序,因此,此时生产者应该还在生产,但这和前提矛盾,故而得证。
2、实现
实际上,我们可以看出,要想正确结束一个多阻塞队列连接的多生产者多消费者的多线程程序,关键在于亮点:
1)主线程要知道第一组生产者何时结束。
2)在第一组生产者结束后,主线程按照阻塞队列的连接顺序对其分别调用join方法。
为什么这么做是对的?我们知道,保证被一个阻塞队列连接起来的多个生产者消费者正确结束,需要保证在对阻塞队列调用join方法以前生产者已经完成任务了。在第一组生产者结束后,此时对于第二组生产者,也就是第一组消费者而言,只需要拿完queue中残留的元素即可。
对于第二组生产者而言,我们可以这么写:
while True:
item = queue1.get()
process(item)
queue2.put(item)
queue1.task_done()
可以看出,在queue1的join方法解除阻塞状态时,此时第二组生产者也已经完成了生产,因为第二组生产者将元素放入下一个阻塞队列在对前一个阻塞队列调用task_done之前(有点拗口。。),故而维持了这一性质。
3、最后的问题:主线程如何得知第一组生产者何时结束?
由于第一组生产者直接和数据源打交道,因此,第一组生产者可以明白自己何时应该结束。具体到我们这个需求,由于给定了ip段,而IP段中的地址是有限的,故而ip生成器完全不需要用while True来保证线程的持续运行,而只需要这么写:
for ip in ip_list:
queue1.put(ip)
在明确了上面这点后,我们可以通过一个比较讨巧和优雅的办法来避免使用复杂而容易出错的notify机制,而让主线程轻易得知第一组生产者线程的结束时机!
那就是!
用一个阻塞队列将主线程和第一组生产者连接起来!
先看代码如何写:
# 主线程:
for thread in first_producer:
thread.set_notify_queue(queue_notify)
thread.start() for key in range(len(first_producer)):
queue_notify.put('any thing u like, i chose string') queue_notify.join() # 第一组生产者线程(可能有多个) queue_notify.get()
for ip in ip_list:
queue1.put(ip)
what_ever_other_thing()
queue_notify.task_done()
假设第一组生产者线程有20个
那么,连接第一组生产者线程和主线程的阻塞队列中,就有20个你喜欢的任意对象。由于每个生产者线程只拿一次,因此,可以保证人人有份。而在第一组生产者线程结束时,会调用一次task_done,因此,在所有第一组生产者线程结束后,主线程就能从queue_notify.join()中被唤醒,进而执行接下来的逻辑。
接下来什么逻辑?
前面说过了,按顺序挨个join你的阻塞队列们。
如此这般,我们就能完美而优雅的解决正确滴结束多个阻塞队列连接下的多生产者多消费者的Python程序这个问题(感觉像绕口令。。)
[原创]如何编写多个阻塞队列连接下的多生产者多消费者的Python程序的更多相关文章
- [原创]Mac系统下制作OS 10.11安装镜像
一.所需软件 1.从App Store下载OS X El Capitan 10.11.2 ------------------------------------------------------- ...
- [原创]纯JS实现网页中多选复选框checkbox和单选radio的美化效果
图片素材: 最终效果图: <html><title> 纯JS实现网页中多选复选框checkbox和单选radio的美化效果</title><head>& ...
- [原创]mac终端前面的计算机名怎么改??
1.修改-之前的名称 mac环境,系统 OS X Yisemite,打开终端, 执行下面命令“Tmp”是你想要改的电脑名称 sudo scutil --set HostName Tmp 执行前,执行后 ...
- [原创]jQuery的this和$(this)
网上有很多关于jQuery的this和$(this)的介绍,大多数只是理清了this和$(this)的指向,其实它是有应用场所的,不能一概而论在jQuery调用成员函数时,this就是指向dom对象. ...
- [原创]阿里云RocketMQ踩过的哪些坑
由于公司的最近开始使用RocketMQ来做支付业务处理, 便开启了学习阿里云RocketMQ的学习与实践之路, 其中踩了不少的坑, 大部份是由于没有仔细查看阿里云的技术文档而踩的坑. 但是有一个非常大 ...
- [原创]delphi在win7下创建共享文件夹源代码
unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms ...
- Java并发指南11:解读 Java 阻塞队列 BlockingQueue
解读 Java 并发队列 BlockingQueue 转自:https://javadoop.com/post/java-concurrent-queue 最近得空,想写篇文章好好说说 java 线程 ...
- Java中的阻塞队列
1. 什么是阻塞队列? 阻塞队列(BlockingQueue)是一个支持两个附加操作的队列.这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空.当队列满时,存储元素的线程会等待队列可用 ...
- java并发:阻塞队列
第一节 阻塞队列 1.1 初识阻塞队列 队列以一种先进先出的方式管理数据,阻塞队列(BlockingQueue)是一个支持两个附加操作的队列,这两个附加的操作是:在队列为空时,获取元素的线程会等待队列 ...
随机推荐
- Java反射机制的使用方法
Java的反射机制同意你在程序执行的过程中获取类定义的细节.有时候在程序执行的时候才得知要调用哪个方法,这时候反射机制就派上用场了. 获取类 类的获取方法有下面几种: forName().通过Clas ...
- 2388 Who's in the Middle(简单排序)
训练计划的第一个问题,首先从水问题开始:排序的数组,中间数则输出. http://poj.org/problem?id=2388 冒泡排序: #include <iostream> usi ...
- 与众不同 windows phone (31) - Communication(通信)之基于 Socket UDP 开发一个多人聊天室
原文:与众不同 windows phone (31) - Communication(通信)之基于 Socket UDP 开发一个多人聊天室 [索引页][源码下载] 与众不同 windows phon ...
- 关于PHP的内置服务器的使用
今天刚开始正式学习PHP(之前有一点了解),推荐学习的网站是w3school.一开始不知道tomcat服务器不支持PHP脚本,直接把.php文件放到tomcat里面去运行,结果嵌入的php代码段没有什 ...
- 旧发票要保留SIRET等信息,或者整个PDF
查看旧发票时,每次都实时生成发票是不行的,因为公司的SIRET居然会是变的!!
- [翻译]利用C#获取终端服务(Terminal Services)会话的闲置时间
[翻译]利用C#获取终端服务(Terminal Services)会话的闲置时间 作者:Tuuzed(土仔) 发表于:2008年2月29日版权声明:可以任意转载,转载时请务必以超链接形式标明文章原 ...
- Linux温馨提示1--安装U板块和Windwos划分
一.安装U盘 现在我用Ubuntu12.04在插入U光盘将被直接安装到/media/下, 10:33linc@Linc-Ubuntu:linc$ df -h Filesystem Size Used ...
- Struts 2.x仍然明显落后于时代。 Struts 2.x这一类老牌Web MVC开发框架仅能用于开发瘦客户端应用,无法用来开发对于交互体验要求更高的应用。
后来我在工作中陆续使用过Struts 1.x和Struts 2.x.我曾经把一个开源的基于Struts 1.x的自助式广告联盟应用移植到Spring MVC,还基于Struts 2.x做过网站开发.S ...
- hdu 1029(hash)
传送门:Ignatius and the Princess IV 题意:给n个数,找出出现次数大于等于(n+1)/2的那个数. 分析:大水题,排个序输出中间那个即可,这里随便写个HASHMAP找出次数 ...
- c++ 如何获取系统时间 - zjnig711的信息仓库 - 博客频道 - CSDN.NET
c++ 如何获取系统时间 - zjnig711的信息仓库 - 博客频道 - CSDN.NET c++ 如何获取系统时间 分类: C/C++ 2008-05-08 22:15 14115人阅读 评论(5 ...