Python 多執行緒無用?深入總結 二(深入瞭解GIL 執行緒守護 執行緒程式CPU關係)

暴躁的熱心網友皮皮文發表於2018-06-09

知識都是由淺入深的過程,之前看的書比較淺,知識表面上使用下多執行緒,對多執行緒程式設計的一些細節說的略淺。在後來的學習中,發現了遺漏了一些details的問題特此補上

1.threading.Tread()直接建立執行緒例項與threading.start_new_thread()的區別是什麼?

答:

最大的區別就是,如果直接new新的執行緒,那麼程式執行到了new_thread會直接開始執行執行緒。但是例項化thread會在例項化結束後,有start命令後才開始執行執行緒,所以我們使用了threading.Thread()。

2.join(timeout=)與setDaemon

join(timeout=),這個方法為threading module比thread module多的方法,也是推薦使用threading的原因:由於threading module中有執行緒守護機制,執行 thread1.join()

那麼thread1執行緒會被守護,其意思就是等待thread1執行完或者在timeout引數規定的等待時間內,thread1一直在執行的。但是如果在thread module裡面如果想完成這樣的功能,就需要引入鎖的概念(分配、獲取、釋放、檢查鎖狀態等),很明顯就太麻煩了。

對於join()方法而言,其另外一個重要方面是其實他根本不需要呼叫。一旦執行緒啟動,他們就會一直執行,知道給定的函式完成後退出。如果主執行緒還有其他事情去做,而不是等待這些執行緒完成(例如處理新的使用者請求),就可以不呼叫join().join方法只有在你需要等待執行緒完成的時候才是有用的。

思辨

我們在使用Python時候說的主執行緒,可以理解為python程式,即主執行緒,python程式coding中啟動的執行緒可以視為子執行緒。

主程式的子程式或者執行緒,如果被set為守護執行緒或者程式那麼,等待主程式或者執行緒執行完畢後,其子執行緒或者程式就直接被銷燬,不會等到其執行完畢後再結束主程式。

Python 多執行緒無用?深入總結 二(深入瞭解GIL 執行緒守護 執行緒程式CPU關係)

圖例中並沒有設定守護執行緒或者等待執行緒。 但是如果我在子執行緒後加了join等待執行緒的話結果如下

Python 多執行緒無用?深入總結 二(深入瞭解GIL 執行緒守護 執行緒程式CPU關係)

此時果然主執行緒等待子執行緒執行結束後再開始執行(可理解為懸停)

Python 多執行緒無用?深入總結 二(深入瞭解GIL 執行緒守護 執行緒程式CPU關係)

此例子為守護執行緒的例子,如圖所示,start前面設定了子執行緒為守護執行緒那麼,主執行緒結束,子執行緒也被迫結束。

注:

主執行緒在其他非守護執行緒結束後,才算真正結束。因為主執行緒結束,意味著程式結束,所以作業系統給程式分配的資源都要被收回,所以必須確保非守護執行緒執行完畢。

3 GIL:

首先需要明確的一點是GIL並不是Python的特性,它是在實現Python解析器(CPython)時所引入的一個概念。 就好比C++是一套語言(語法)標準,但是可以用不同的編譯器來編譯成可執行程式碼。有名的編譯器例如GCC,INTEL C++,Visual C++等。Python也一樣,同樣一段程式碼可以通過CPython,PyPy,Psyco等不同的Python執行環境來執行。像其中的JPython就沒有GIL。然而因為CPython是大部分環境下預設的Python執行環境。所以在很多人的概念裡CPython就是Python,也就想當然的把GIL歸結為Python語言的缺陷。所以這裡要先明確一點:GIL並不是Python的特性,Python完全可以不依賴於GIL.

介紹:

GIL本質上就是一把互斥鎖,既然是互斥鎖那麼都是將並行變為序列,從而保證同一時間內資料只能被一個任務修改,進而保證資料安全。每次執行python程式的時候,都會產生一個獨立的程式。例如python a.py b.py c.py,這樣就啟動了三個不同的python。

那麼對於一個程式中,含有主執行緒,還含有其他因為主執行緒開啟的執行緒,還有直譯器開啟的垃圾回收等直譯器級別的執行緒,總之,所有執行緒都執行在這個程式內。

實驗

在一個python指令碼中定義一個函式work,啟動三個執行緒訪問它(target=work),如果可以呼叫,我們就可以理解為執行緒訪問成功。,這個coding是執行緒池中共享的。

Python 多執行緒無用?深入總結 二(深入瞭解GIL 執行緒守護 執行緒程式CPU關係)

多個執行緒target=work,那麼執行流程就是每一個執行緒拿到執行許可權,然後交到直譯器中去解釋。 那麼這就導致了一個問題,同一行資料100,那麼執行緒執行x=100的時候,垃圾回收執行緒要收回x=100,如下加GIL鎖,保證python直譯器同一時間執行一個任務程式碼。

4 Python 多執行緒到底有用嗎?

先思考三個問題:

1.CPU是用來幹嘛的(計算還是I/O)?

2.多cpu,意味著可以有多個核並行完成計算,所以多核提升的是計算效能。

  1. 每個cpu一旦遇到I/O阻塞,仍然需要等待,所以多核對I/O操作沒什麼用處 。

舉一個例子,計算相當於吃飯,CPU相當於一個人,那麼我們一個人吃飯快,還是多個人吃飯快?當然是多個人,所以多個CPU是效能提升。I/O阻塞相當於,上菜阻塞,那麼如果是I/O密集型的飯局,一隻不上菜,叫一票人也沒用。反過來講如果上菜充足,那當然是人越多越好。

所以對於計算來說CPU越多越好,但是對於I/O來說,再多CPU也沒用,當然對於一個python程式來說CPU多,肯定執行效率會有所提高。因為一個py程式並不是純計算或者是I/O密集的程式,所以只能去看程式是I/O密集型的還是計算密集的。

案例:如果我們有四個任務需要處理,那麼:

解決方案

1.四個程式

2.一個程式:下四個執行緒

單核情況下:

1.如果四個任務是計算密集型,沒有多核來平行計算,方案一徒增了建立程式的開銷,方案二勝 2.如果四個任務是I/O密集型,方案一建立程式的開銷大,且程式的切換速度遠不如執行緒,方案二勝

多核情況下:

1.如果四個任務是計算密集型,多核意味著平行計算,在python中一個程式中同一時刻只有一個執行緒執行用不上多核,方案一勝 2.如果四個任務是I/O密集型,再多的核也解決不了I/O問題,方案二勝

結論

現在電腦都是多核的,對於計算密集型的多執行緒並沒有多大的提升效能,但是如果是I/O密集性,多執行緒還是有顯著的提升的。

相關文章