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 是個很酷的小功能,也是簡化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之後,在我本機上的運行效果已相差無幾。
生成上千張圖像的縮略圖:
如今我們看一年計算密集型的任務!我最常遇到的這類問題之一就是大量圖像文件夾的處理。
其中一項任務就是建立縮略圖。這也是併發中比較成熟的一項功能了。
基礎單線程建立過程
做爲示例來講稍微有點複雜。但其實就是傳一個文件夾目錄進來,獲取到裏面全部的圖片,分別建立好縮略圖而後保存到各自的目錄當中。
在個人電腦上,處理大約6000張圖片大約耗時27.9秒.
若是使用併發map處理替代其中的for循環:
就改了幾行代碼速度卻能獲得如此巨大的提高。最終版本的處理速度還要更快。由於咱們將計算密集型與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)進程、線程和協程的理解
(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進行併發編程