【python技巧系列】python的multiprocessing到底怎麼用的問題

衆所周知,因爲python(Cpython)的全局鎖(GIL)問題存在,致使Thread也就是線程的並行並不可實現。 multiprocessing 模塊採用多進程而不是多線程的方式實現並行,解決了GIL的問題,必定程度上使情況獲得了緩解。html

然而,Multiprocess自己依然有一些功能上的瓶頸。其中一個重要的是:進程之間不能共享內存(線程間則能夠共享內存)。這意味着在進程間交換數據的時候,須要把數據打包、傳遞,解包。在python的語境下就是:python

"pickle from main process to the subprocess;git

depickle from subprocess to an object in memory;github

pickle and return to the main process;多線程

depickle from main process and return to memory"dom

(具體詳見這個問題下的吐槽)函數

所以, 在須要在進程間共享巨大的數據包的時候,多進程的表現還不如單進程。測試

 

除此以外,當須要運行的程序自己不是計算密集型而是是IO密集型,多進程所增長的讀寫會抵消掉運算速度的增益;若是程序複雜度根本不須要用並行來解決,那麼創建進程(池)的時間極可能比運行程序自己還要慢;另外,在進程池 multiprocessing.Pool(n) 的 n  的選擇上,若是選擇了多於當前CPU的核心數目的數字( multiprocessing.cpu_count() ),那麼在進程之間切換的功夫會大大拉低效率。ui

 

創建對線程和進程關係的直觀印象,可參考這篇文章spa

快速而完整地瞭解python的全局鎖(GIL)問題,參考這篇不錯的博客

 


 

爲了解 multiprocess 的使用,我作了一些測試,測試環境是4核的Macbook Air。以下:

from multiprocessing import Process, Manager, Pool

 

 1 def f(l):
 2     l.reverse()
 3     return
 4     
 5 def main():
 6     l1 = [random.randrange(0, 100000, 1) for i in range(0, 100000)]
 7     l2 = [random.randrange(0, 100000, 1) for i in range(0, 100000)] 
 8     l3 = [random.randrange(0, 100000, 1) for i in range(0, 100000)] 
 9     l4 = [random.randrange(0, 100000, 1) for i in range(0, 100000)] 
10     l5 = [random.randrange(0, 100000, 1) for i in range(0, 100000)] 
11     l6 = [random.randrange(0, 100000, 1) for i in range(0, 100000)] 
12     l7 = [random.randrange(0, 100000, 1) for i in range(0, 100000)] 
13     s = time.time() 
14     for l in [l1, l2, l3, l4, l5, l6, l7]:
15         f(l)
16     print "%s seconds" % (time.time() - s)
17     s = time.time()
18     map(f, [l1, l2, l3, l4, l5, l6, l7])
19     print "%s seconds" % (time.time() - s)
20     p = Pool(4)
21     s = time.time() 
22     p.map(f, [l1, l2, l3, l4, l5, l6, l7])
23     print "%s seconds" % (time.time() - s)
24     return

也就是分別測試 f() 對 l1, l2, l3, l4, l5, l6, l7 7個列表的操做時間。先是循環的依次操做,再是python中很是好用的 map() 函數,最後是 multiprocessing 的進程池 multiprocessing.Pool.map() ——進程池中創建了4個 worker process , 也就是說,接下來的任務會被隨機地分配給4個進程來完成。

每次操做以前都從新計時,獲得了這樣的結果:

>>> main()
0.00250101089478 seconds
0.000663995742798 seconds
0.907639980316 seconds

多進程出奇得慢。而 map() 相對於循環操做有很大的效率提高。

因此並非全部任務都適合多進程(至於列表的倒置爲何不適合多進程,我並不明白。。)。在最近的實驗中,我須要找到提高效率的方法,之後的測試會陸續放上來。

 


 

實驗的時候,原本用 Pool(n) ,可是始終卡在了 racquire() 這裏(ctrl-c終止後停在的地方),因而改用 Process() ,就能夠運行了。可是運行到中間的某一行後,原本設定的4個進程變成了只有1個進程在運行,緣由是內存超了。。

另外還有一個讓人頭疼的問題。在用 Process()的時候,若是有一個進程滅了,那麼你不會收到任何提示 —— 沒錯,就是悄無聲息地沒了。。

相關文章
相關標籤/搜索