(转)python之并行任务的技巧

Python的并发处理能力臭名昭著。先撇开线程以及GIL方面的问题不说,我以为多线程问题的根源不在技术上而在于理念。大部分关于Pyhon线程和多进程的资料虽然都很不错,但却过于细节。这些资料讲的都是有始无终,到了真正实际使用的部分却草草结束了。html

传统例子

在DDG https://duckduckgo.com/ 搜索“Python threading tutorial”关键字,结果基本上却都是相同的类+队列的示例。
标准线程多进程,生产者/消费者示例:java

请输入图片描述

这里是代码截图,若是用其余模式贴出大段代码会很不美观。文本模式点这里 here
Mmm.. 感受像是java代码
在此我不想印证采用生产者/消费者模式来处理线程/多进程是错误的— 确实没问题。实际上这也是解决不少问题的最佳选择。可是,我却不认为这是平常工做中经常使用的方式。python

问题所在

一开始,你须要一个执行下面操做的铺垫类。接着,你须要建立一个传递对象的队列,并在队列两端实时监听以完成任务。(颇有可能须要两个队列互相通讯或者存储数据)
Worker越多,问题越大.
下一步,你可能会考虑把这些worker放入一个线程池一边提升Python的处理速度。下面是
IBM tutorial 上关于线程较好的示例代码。这是你们经常使用到的利用多线程处理web页面的场景nginx

请输入图片描述

Seriously, Medium. Fix your code support. Code is Here.git

感受效果应该很好,可是看看这些代码!初始化方法、线程跟踪,最糟的是,若是你也和我同样是个容易犯死锁问题的人,这里的join语句就要出错了。这样就开始变得更加复杂了!
到如今为止都作了些什么?基本上没什么。上面的代码都是些基础功能,并且很容易出错。(天啊,我忘了写上在队列对象上调用task_done()方法(我懒得修复这个问题在从新截图)),这真是性价比过低。所幸的是,咱们有更好的办法.程序员

引入:Map

Map 是个很酷的小功能,也是简化Python并发代码的关键。对那些不太熟悉Map的来讲,它有点相似Lisp.它就是序列化的功能映射功能. e.g.github

urls = [', '] results = map(urllib2.urlopen, urls) 

这里调用urlopen方法,并把以前的调用结果全都返回并按顺序存储到一个集合中。这有点相似web

results = []
for url in urls: results.append(urllib2.urlopen(url)) 

Map可以处理集合按顺序遍历,最终将调用产生的结果保存在一个简单的集合当中。
为何要提到它?由于在引入须要的包文件后,Map能大大简化并发的复杂度!编程

请输入图片描述

支持Map并发的包文件有两个:
Multiprocessing,还有少为人知的但却功能强大的子文件 multiprocessing.dummy. .网络

Digression这是啥东西?没据说过线程引用叫dummy的多进程包文件。我也是直到最近才知道。它在多进程的说明文档中也只被提到了一句。它的效果也只是让你们直到有这么个东西而已。这可真是营销的失误!

Dummy是一个多进程包的完整拷贝。惟一不一样的是,多进程包使用进程,而dummy使用线程(天然也有Python自己的一些限制)。因此一个有的另外一个也有。这样在两种模式间切换就十分简单,而且在判断框架调用时使用的是IO仍是CPU模式很是有帮助。

准备开始

准备使用带有并发的map功能首先要导入相关包文件:

from multiprocessing import Pool from multiprocessing.dummy import Pool as ThreadPool 

而后初始化:

pool = ThreadPool() 

就这么简单一句解决了example2.py中build_worker_pool的功能. 具体来说,它首先建立一些有效的worker启动它并将其保存在一些变量中以便随时访问。
pool对象须要一些参数,但如今最紧要的就是:进程。它能够限定线程池中worker的数量。若是不填,它将采用系统的内核数做为初值。

通常状况下,若是你进行的是计算密集型多进程任务,内核越多意味着速度越快(固然这是有前提的)。但若是是涉及到网络计算方面,影响的因素就千差万别。因此最好仍是能给出合适的线程池大小数。

pool = ThreadPool(4) # Sets the pool size to 4 

若是运行的线程不少,频繁的切换线程会十分影响工做效率。因此最好仍是能经过调试找出任务调度的时间平衡点。
好的,既然已经建好了线程池对象还有那些简单的并发内容。我们就来重写一些example2.py中的url opener吧!

请输入图片描述

看吧!只用4行代码就搞定了!其中三行仍是固定写法。使用map方法简单的搞定了以前须要40行代码作的事!为了增长趣味性,我分别统计了不一样线程池大小的运行时间。

请输入图片描述

结果:

请输入图片描述

效果惊人!看来调试一下确实颇有用。当线程池大小超过9之后,在我本机上的运行效果已相差无几。

示例 2:

生成上千张图像的缩略图:
如今我们看一年计算密集型的任务!我最常遇到的这类问题之一就是大量图像文件夹的处理。
其中一项任务就是建立缩略图。这也是并发中比较成熟的一项功能了。
基础单线程建立过程

请输入图片描述

做为示例来讲稍微有点复杂。但其实就是传一个文件夹目录进来,获取到里面全部的图片,分别建立好缩略图而后保存到各自的目录当中。
在个人电脑上,处理大约6000张图片大约耗时27.9秒.
若是使用并发map处理替代其中的for循环:
请输入图片描述

只用了5.6 秒!

就改了几行代码速度却能获得如此巨大的提高。最终版本的处理速度还要更快。由于咱们将计算密集型与IO密集型任务分派到各自独立的线程和进程当中,这也许会容易形成死锁,但相对于map强劲的功能,经过简单的调试咱们最终总能设计出优美、高可靠性的程序。就如今而言,也别无它法。
好了。来感觉一下一行代码的并发程序吧。

(1)英文原文:https://medium.com/p/40e9b2b36148

(2)原文代码:https://github.com/chriskiehl/Blog/tree/master/40e9b2b36148

(3)关于Python并行任务技巧的几点补充 http://liming.me/2014/01/12/python-multitask-fixed/

(4)在单核 CPU、Python GIL 限制下,多线程须要加锁吗?

https://github.com/onlytiancai/codesnip/blob/master/python/sprace.py

(5)gevent程序员指南  http://xlambda.com/gevent-tutorial/#_8

(6)进程、线程和协程的理解

http://blog.leiqin.name/2012/12/02/%E8%BF%9B%E7%A8%8B%E3%80%81%E7%BA%BF%E7%A8%8B%E5%92%8C%E5%8D%8F%E7%A8%8B%E7%9A%84%E7%90%86%E8%A7%A3.html

(7)python 多进程: from multiprocessing.pool import ThreadPool
http://hi.baidu.com/0xcea4/item/ddd133c187a6277089ad9e4b

http://outofmemory.cn/code-snippet/6723/Python-many-process-bingfa-multiprocessing

(8)python的threading和multiprocessing模块初探

http://blog.csdn.net/zhaozhi406/article/details/8137670

(9)使用Python进行并发编程

http://python.jobbole.com/81255/