前言
近年來,使用python的人愈來愈多,這得益於其清晰的語法、低廉的入門代價等因素。儘管python受到的關注日益增多,但python並不是完美,例如被人詬病最多的GIL(值得注意的是,GIL並不是python特性,它是在實現Python解析器(CPython)時所引入的一個概念,而CPython是大部分環境下默認的Python執行環境),全稱Global Interpreter Lock。從官方定義來看,GIL無疑就是一把全局排他鎖,會嚴重影響python多線程的效率,甚至幾乎等於Python是個單線程程序。python
爲了知足開發者的需求,python社區推出了multiprocessing。顧名思義,multiprocessing使用了多進程而不是多線程,每一個進程有本身的獨立的GIL,所以也不會出現進程之間的GIL爭搶。固然multiprocessing也並不是完美,例如增長了數據通信的難度等方面。講了這麼多背景,下面分享一下使用multiprocessing踩過的坑。因爲這篇博客偏向實際工程,主要分享應用經驗,相關基礎知識能夠查閱Python Documentation。多線程
系統
>>> import sys
>>> print(sys.version)
3.6.0 |Anaconda 4.3.1 (64-bit)| (default, Dec 23 2016, 12:22:00) \n[GCC 4.4.7 20120313 (Red Hat 4.4.7-1)]
1
2
3
死鎖
百度百科對死鎖的定義優化
死鎖是指兩個或兩個以上的進程在執行過程當中,因爲競爭資源或者因爲彼此通訊而形成的一種阻塞的現象,若無外力做用,它們都將沒法推動下去。此時稱系統處於死鎖狀態或系統產生了死鎖,這些永遠在互相等待的進程稱爲死鎖進程1。this
從定義可知,永遠互相等待是死鎖的一個重要特徵。在multiprocessing中,你稍不留神,也會犯這種錯誤,例如:.net
from multiprocessing import Process, Queue線程
def f(q):
q.put('X' * 1000000)blog
if __name__ == '__main__':
queue = Queue()
p = Process(target=f, args=(queue,))
p.start()
p.join() # this deadlocks
1
2
3
4
5
6
7
8
9
10
結果是死鎖。當一個進程被join時,Python會檢查被放入Queue中的數據是否已經所有刪除(例如Queue.get),若沒有刪除,則進程會一直處於等待狀態。發現這種狀況時,一方面感嘆「你讓我找的好苦啊」,另外一方面思考python的開發者怎麼會對這種狀況坐視不理呢?是否作了某些嘗試?例如若Queue小於某個閾值,進程join會將其視爲空Queue。基於這種猜測,作了如下實驗進程
from multiprocessing import Process, Queue
import timeip
def f(q):
num = 10000
q.put('X' * num)
print("Finish put....")資源
if __name__ == '__main__':
queue = Queue()
p = Process(target=f, args=(queue,))
p.start()
print("Start to sleep...")
time.sleep(2)
print("Wake up....")
p.join()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
結果是程序正常結束,必定程度上驗證了個人猜測。爲了進一步肯定猜測的正確性,我又作了num=1000、num=100和num=1的實驗,結果均是程序正常結束,證實進程join時的確會判斷Queue的大小,從而避免死鎖。儘管這種策略有必定效果,但並不能根治死鎖,因此進程join時必定要保證Queue中數據已經被所有取走。
除了上述的狀況外,進程join自身、終止帶鎖的進程等狀況也會致使死鎖,之後會慢慢分享給你們。