Python 并行任務技巧

jopen 9年前發布 | 37K 次閱讀 Python Python開發

內容來自互聯網,英文原文:https://medium.com/@thechriskiehl/parallelism-in-one-line-40e9b2b36148

Python的并發處理能力臭名昭著。先撇開線程以及GIL方面的問題不說,我覺得多線程問題的根源不在技術上而在于理念。大部分關于Pyhon線程和多進程的資料雖然都很不錯,但卻過于細節。這些資料講的都是虎頭蛇尾,到了真正實際使用的部分卻草草結束了。

傳統例子

標準線程多進程,生產者/消費者示例:

Python 并行任務技巧這里是代碼截圖,如果用其他模式貼出大段代碼會很不美觀。 

在此我不想印證采用生產者/消費者模式來處理線程/多進程是錯誤的— 確實沒問題。實際上這也是解決很多問題的最佳選擇。但是,我卻不認為這是日常工作中常用的方式。


問題所在

一開始,你需要一個執行下面操作的鋪墊類。接著,你需要創建一個傳遞對象的隊列,并在隊列兩端實時監聽以完成任務。(很有可能需要兩個隊列互相通信或者存儲數據)

Worker越多,問題越大.

下一步,你可能會考慮把這些worker放入一個線程池一邊提高Python的處理速度。下面是
IBM tutorial 上關于線程較好的示例代碼。這是大家常用到的利用多線程處理web頁面的場景

Python 并行任務技巧 


感覺效果應該很好,但是看看這些代碼!初始化方法、線程跟蹤,最糟的是,如果你也和我一樣是個容易犯死鎖問題的人,這里的join語句就要出錯了。這樣就開始變得更加復雜了!

到現在為止都做了些什么?基本上沒什么。上面的代碼都是些基礎功能,而且很容易出錯。(天啊,我忘了寫上在隊列對象上調用task_done()方法(我懶得修復這個問題在重新截圖)),這真是性價比太低。所幸的是,我們有更好的辦法.

引入:Map

Map是個很酷的小功能,也是簡化Python并發代碼的關鍵。對那些不太熟悉Map的來說,它有點類似List.它就是序列化的功能映射功能. e.g.

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

 

這里調用urlopen方法,并把之前的調用結果全都返回并按順序存儲到一個集合中。這有點類似

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

 

Map能夠處理集合按順序遍歷,最終將調用產生的結果保存在一個簡單的集合當中。

為什么要提到它?因為在引入需要的包文件后,Map能大大簡化并發的復雜度!


Python 并行任務技巧



支持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吧!

Python 并行任務技巧


看吧!只用4行代碼就搞定了!其中三行還是固定寫法。使用map方法簡單的搞定了之前需要40行代碼做的事!為了增加趣味性,我分別統計了不同線程池大小的運行時間。

Python 并行任務技巧

結果:

Python 并行任務技巧

效果驚人!看來調試一下確實很有用。當線程池大小超過9以后,在我本機上的運行效果已相差無幾。


示例 2:

生成上千張圖像的縮略圖:

現在咱們看一年計算密集型的任務!我最常遇到的這類問題之一就是大量圖像文件夾的處理。

其中一項任務就是創建縮略圖。這也是并發中比較成熟的一項功能了。

基礎單線程創建過程

Python 并行任務技巧

作為示例來說稍微有點復雜。但其實就是傳一個文件夾目錄進來,獲取到里面所有的圖片,分別創建好縮略圖然后保存到各自的目錄當中。

在我的電腦上,處理大約6000張圖片大約耗時27.9秒.

如果使用并發map處理替代其中的for循環:

Python 并行任務技巧


只用了5.6 !

就改了幾行代碼速度卻能得到如此巨大的提升。最終版本的處理速度還要更快。因為我們將計算密集型與IO密集型任務分派到各自獨立的線程和進程當中,這也許 會容易造成死鎖,但相對于map強勁的功能,通過簡單的調試我們最終總能設計出優美、高可靠性的程序。就現在而言,也別無它法。

好了。來感受一下一行代碼的并發程序吧。

來自:http://blog.sina.com.cn/s/blog_6708cadf0101h8us.html

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!