爲何有人說 Python 多線程是雞肋?

爲何有人會說 Python 多線程是雞肋?知乎上有人提出這樣一個問題,在咱們常識中,多進程、多線程都是經過併發的方式充分利用硬件資源提升程序的運行效率,怎麼在 Python 中反而成了雞肋?python

有同窗可能知道答案,由於 Python 中臭名昭著的 GIL,GIL 是什麼?爲何會有 GIL?多線程真的是雞肋嗎? GIL 能夠去掉嗎?帶着這些問題,咱們一塊兒往下看,同時須要你有一點點耐心。安全

多線程是否是雞肋,咱們先作個實驗,實驗很是簡單,就是將數字 "1億" 遞減,減到 0 程序就終止,這個任務若是咱們使用單線程來執行,完成時間會是多少?使用多線程又會是多少?show me the code服務器

# 任務
def decrement(n):
    while n > 0:
        n -= 1
複製代碼

單線程數據結構

import time

start = time.time()
decrement(100000000)
cost = time.time() - start
>>> 6.541690826416016
複製代碼

在個人4核 CPU 計算機中,單線程所花的時間是 6.5 秒。可能有人會問,線程在哪裏?其實任何程序運行時,默認都會有一個主線程在執行。(關於線程與進程這裏不展開,我會單獨開一篇文章)多線程

多線程併發

import threading

start = time.time()

t1 = threading.Thread(target=decrement, args=[50000000])
t2 = threading.Thread(target=decrement, args=[50000000])

t1.start() # 啓動線程,執行任務
t2.start() # 同上

t1.join() # 主線程阻塞,直到t1執行完成,主線程繼續日後執行
t2.join() # 同上

cost = time.time() - start

>>>6.85541033744812
複製代碼

建立兩個子線程 t一、t2,每一個線程各執行 5 千萬次減操做,等兩個線程都執行完後,主線程終止程序運行。結果,兩個線程以合做的方式執行是 6.8 秒,反而變慢了。按理來講,兩個線程同時並行地運行在兩個 CPU 之上,時間應該減半纔對,如今不減反增。測試

是什麼緣由致使多線程不快反慢的呢?spa

緣由就在於 GIL ,在 Cpython 解釋器(Python語言的主流解釋器)中,有一把全局解釋鎖(Global Interpreter Lock),在解釋器解釋執行 Python 代碼時,先要獲得這把鎖,意味着,任什麼時候候只可能有一個線程在執行代碼,其它線程要想得到 CPU 執行代碼指令,就必須先得到這把鎖,若是鎖被其它線程佔用了,那麼該線程就只能等待,直到佔有該鎖的線程釋放鎖纔有執行代碼指令的可能。線程

所以,這也就是爲何兩個線程一塊兒執行反而更加慢的緣由,由於同一時刻,只有一個線程在運行,其它線程只能等待,即便是多核CPU,也沒辦法讓多個線程「並行」地同時執行代碼,只能是交替執行,由於多線程涉及到上線文切換、鎖機制處理(獲取鎖,釋放鎖等),因此,多線程執行不快反慢。設計

何時 GIL 被釋放呢?

當一個線程遇到 I/O 任務時,將釋放GIL。計算密集型(CPU-bound)線程執行 100 次解釋器的計步(ticks)時(計步可粗略看做 Python 虛擬機的指令),也會釋放 GIL。能夠經過 sys.setcheckinterval()設置計步長度,sys.getcheckinterval() 查看計步長度。相比單線程,這些可能是多線程帶來的額外開銷

CPython 解釋器爲何要這樣設計?

多線程是爲了適應現代計算機硬件高速發展充分利用多核處理器的產物,經過多線程使得 CPU 資源能夠被高效利用起來,Python 誕生於1991年,那時候硬件配置遠沒有今天這樣豪華,如今一臺普通服務器32核64G內存都不是什麼司空見慣的事,可是多線程有個問題,怎麼解決共享數據的同步、一致性問題,由於,對於多個線程訪問共享數據時,可能有兩個線程同時修改一個數據狀況,若是沒有合適的機制保證數據的一致性,那麼程序最終致使異常,因此,Python之父就搞了個全局的線程鎖,無論你數據有沒有同步問題,反正一刀切,上個全局鎖,保證數據安全。這也就是多線程雞肋的緣由,由於它沒有細粒度的控制數據的安全,而是用一種簡單粗暴的方式來解決。

這種解決辦法放在90年代,實際上是沒什麼問題的,畢竟,那時候的硬件配置還很簡陋,單核 CPU 仍是主流,多線程的應用場景也很少,大部分時候仍是以單線程的方式運行,單線程不要涉及線程的上下文切換,效率反而比多線程更高(在多核環境下,不適用此規則)。因此,採用 GIL 的方式來保證數據的一致性和安全,未必不可取,至少在當時是一種成本很低的實現方式。

那麼把 GIL 去掉可行嗎?

還真有人這麼幹多,可是結果使人失望,在1999年Greg Stein 和Mark Hammond 兩位哥們就建立了一個去掉 GIL 的 Python 分支,在全部可變數據結構上把 GIL 替換爲更爲細粒度的鎖。然而,作過了基準測試以後,去掉GIL的 Python 在單線程條件下執行效率將近慢了2倍。

Python之父表示:基於以上的考慮,去掉GIL沒有太大的價值而沒必要花太多精力。

小結

CPython解釋器提供了GIL(全局解釋器鎖)保證線程數據同步,那麼有了 GIL,咱們還須要線程同步嗎?多線程在IO密集型任務中,表現又怎樣呢?歡迎你們留言

公衆號:Python之禪

相關文章
相關標籤/搜索