雞肋?Python中的多線程與多進程那些事

雞肋?Python中的多線程與多進程那些事

雞肋? Python中的多線程與多進程那些事

GIL 的存在一直是富有爭議的,它導致 Python 程序無法真正利用現代操作系統的多進程特性。需要注意的是,對於 I/O 圖形處理、NumPy 數學計算這樣的耗時操作都發生在 GIL 之外,實際上基本不受影響,真正受影響的都是 Python 字節碼的執行,GIL 會導致性能瓶頸的出現。總之,只有在使用純 Python 做 CPU 密集的多線程運算時 GIL 會是問題。

GIL是什麼

Python的代碼執行由python虛擬機(也叫解釋器主循環,CPython版本)來控制,Python在設計之初就考慮到在解釋器的主循環中,同時只有一個線程在運行。即每個CPU在任意時刻只有一個線程在解釋器中運行。對python虛擬機訪問的控制由全局解釋鎖GIL控制,正是這個鎖來控制同一時刻只有一個線程能夠運行。——(在單核CPU下的多線程其實都只是併發,不是並行) 。

併發與並行區別

  • 併發:兩個或多個事件在同一時間間隔發生,或者說交替做不同事件的能力,或者說不同的代碼塊交替執行。
  • 並行:兩個或者多個事件在同一時刻發生,或者說同時做不同事件的能力,或者說不同的代碼塊同時執行。

併發和並行的意義

併發和並行都可以處理“多任務”,二者的主要區別在於是否是“同時進行”多個的任務。但是涉及到任務分解(有先後依賴耦合度高的任務無法做到並行)、任務運行(可能要考慮互斥、鎖、共享等)、結果合併。

雞肋?Python中的多線程與多進程那些事

python 下的多線程

Python 下的多線程

在Python多線程下,每個線程的執行方式

  1. 獲取GIL
  2. 切換到這個線程去執行
  3. 運行代碼,這裡有兩種機制:
  4. 指定數量的字節碼指令(100個)
  5. 固定時間15ms線程主動讓出控制
  6. 把線程設置為睡眠狀態
  7. 釋放GIL
  8. 再次重複以上步驟

在Python2中,在解釋器解釋執行任何 Python 代碼時,都需要先獲得這把鎖才行(同一時間只會有一個獲得了 GIL 的線程在跑,其它的線程都處於等待狀態等著 GIL 的釋放),在遇到 I/O 操作時會釋放這把鎖。如果是純計算的程序,沒有 I/O 操作,解釋器會每隔 100 次操作就釋放這把鎖,讓別的線程有機會執行(這個次數可以通過 sys.setcheckinterval 來調整)也正是這種設定,是的多線程的CPU密集型計算非常雞肋,下面會講到為何如此。

而在python3中,GIL不使用ticks計數(100次,釋放GIL),改為使用計時器(執行時間達到15ms閾值後,當前線程釋放 GIL),使得執行計算的次數更多,釋放次數減少,這樣對CPU密集型程序更加友好,但依然沒有解決GIL導致的同一時間只能執行一個線程的問題,所以效率依然不盡如人意。

雞肋?Python中的多線程與多進程那些事

那麼是不是python的多線程是雞肋嘛?

那麼是不是Python的多線程是雞肋嘛?

CPU密集型(各種循環處理、計數等等),在這種情況下,ticks計數很快就會達到閾值,然後觸發GIL的釋放與再競爭(多個線程來回切換是需要消耗資源的),所以python下的多線程對 CPU密集型代碼並不友好,會觸發相當頻繁的線程切換。

IO密集型(文件處理、網絡爬蟲等),多線程能夠有效提升效率(單線程下有IO操作會進行IO 等待,造成不必要的時間浪費,而開啟多線程能在線程A等待時,自動切換到線程B,可以不浪費 CPU的資源,從而能提升程序執行效率,一個線程獲得GIL發送消息,然後等待返回消息(阻塞),Python此時釋放GIL, 其他線程得到GIL發送消息,然後同樣等待返回消息(阻塞)......,這樣保證了IO傳輸過程時間的合理利用,減少了IO等待造成的資源浪費,提高IO傳輸效率)。所以python的多線程對IO密集型代碼比較友好。

雞肋?Python中的多線程與多進程那些事

結論

I/O密集型使用多線程併發執行提高效率、計算密集型使用多進程(multiprocessing )並行執行提高效率。通常程序既包含IO操作又包含計算操作,那麼這種情況下,在開始併發任務之前,可以先進行測試,測試多線程、多進程哪個效率高就是用哪種方式。

請注意:多核多線程比單核多線程更差,多核多進程下,CPU1釋放GIL後,其他CPU上的線程都會進行競爭,但GIL可能會馬上又被CPU1拿到,CPU2釋放GIL後……,導致其他幾個CPU上被喚醒後的線程會醒著等待到切換時間後又進入待調度狀態,這樣會造成線程顛簸(thrashing),導致效率更低。

多線程下的CPU密集型計算也不是無藥可醫,可以利用ctypes繞過GIL,ctypes可以使py直接調用任意的C動態庫的導出函數。所要做的只是把關鍵部分用 C/C++ 寫成 Python 擴展。而且,ctypes會在調用C函數前釋放GIL。

同時,可以瞭解下協程,又稱微線程。

協程最大的優勢就是協程極高的執行效率。因為子程序切換不是線程切換,而是由程序自身控制,因此,沒有線程切換的開銷,和多線程比,線程數量越多,協程的性能優勢就越明顯。

第二大優勢就是不需要多線程的鎖機制,因為只有一個線程,也不存在同時寫變量衝突,在協程中控制共享資源不加鎖,只需要判斷狀態就好了,所以執行效率比多線程高很多。

因為協程是一個線程執行,那怎麼利用多核CPU呢?最簡單的方法是多進程+協程,既充分利用多核,又充分發揮協程的高效率,可獲得極高的性能。

轉載請說明出處哦,多多關注,更多分享。


分享到:


相關文章: