(轉)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/