結論:python多進程間用Queue通信時,如果子進程操作Queue滿了或者內容比較大的情況下,該子進程會阻塞等待取走Queue內容(如果Queue數據量比較少,不會等待),如果調用join,主進程將處於等待,等待子進程結束,造成死鎖
解決方式:在調用join前,及時把Queue的數據取出,而且Queue.get需要在join前
原理分析
模擬子進程阻塞:
-
from multiprocessing import Process, Queue
-
-
-
def fun(q):
-
num = 1000000000
-
q.put( '=' * num)
-
print( "done put")
-
-
-
if __name__ == '__main__':
-
queue = Queue()
-
p = Process(target=fun, args=(queue,))
-
p.start()
-
p.join()
-
print( "done")
原因分析:
-
#
-
# Queue type using a pipe, buffer and thread
-
#
-
-
class Queue(object):
multiprocessing.Queue底層是基於Pipe構建,操作系統管道不是無限長,因此子進程在執行put()期間,處於阻塞,直到某些其他進程使用get()從隊列中取走數據。上例中,主進程等待子進程,打印不了done
而當隊列put數據比較少時,是沒有問題的,先打印done put,再打印done,但這樣寫法是有隱患,當put數據比較多時,就會阻塞
-
from multiprocessing import Process, Queue
-
-
-
def fun(q):
-
num = 1
-
q.put( '=' * num)
-
print( "done put")
-
-
-
if __name__ == '__main__':
-
queue = Queue()
-
p = Process(target=fun, args=(queue,))
-
p.start()
-
p.join()
-
print( "done")
正確的寫法
-
from multiprocessing import Process, Queue
-
-
-
def fun(q):
-
num = 1000000000
-
q.put( '=' * num)
-
print( "done put")
-
-
-
if __name__ == '__main__':
-
queue = Queue()
-
p = Process(target=fun, args=(queue,))
-
p.start()
-
queue.get()
-
p.join()
-
print( "done")
在join前面調用queue.get
注意!!!以下這樣寫法也是不對的,join要在queue.get前面,不然主進程等待子進程結束,而子進程等待隊列數據取走,造成死鎖
-
p = Process(target=fun, args=(queue,))
-
p.start()
-
p.join()
-
queue.get()
Python多線程補充
Python 是一門解釋型語言,它的執行是由解釋器來控制的。
GIL,全稱是 Global Interpreter Lock
,全局解釋鎖 ,專門給解釋器用
一般情況下在用戶態下是無法做到線程級別的時間片輪轉
但是 python 能做到!python 里,解釋器可以記錄每一個線程執行了多長時間——時間一到,就能夠切換到另一條線程。
GIL 就是拿來給線程加鎖的,當一個線程將要執行時,解釋器會把 GIL 鎖給這個線程,其他線程因為沒有鎖,是無法運行的。等到持有鎖線程阻塞或者運行 100 個字節碼,解釋器就會把鎖交給其他線程。
但是這個 GIL 鎖是全局(Global)的,也就導致即使是多核情況下,一次也只有一個線程能運行,從整體上看,整個程序是串行的。
python多線程應用
拿爬蟲程序來說吧,單個爬蟲總會花時間在下載網頁上,很多 CPU 時間就浪費掉了,提供 sleep 機制后,這些爬蟲可以在等待下載時釋放 GIL 鎖,把機會讓給其他爬蟲,這樣整體運行速度能夠得到大幅提升
也就是說 Python 的多線程適合 I/O 密集型的程序,但是對計算密集型程序就不那么友好了
對於計算密集型程序用多進程或者讓 python 調用 C 語言的代碼,在 C 語言里實現多線程