python多執行緒GIL的問題記錄

2022-04-20 10:01:14 字數 4340 閱讀 8060

由於物理上得限制,各cpu廠商在核心頻率上的比賽已經被多核所取代。為了更有效的利用多核處理器的效能,就出現了多執行緒的程式設計方式,而隨之帶來的就是執行緒間資料一致性和狀態同步的困難。即使在cpu內部的cache也不例外,為了有效解決多份快取之間的資料同步時各廠商花費了不少心思,也不可避免的帶來了一定的效能損失。

python當然也逃不開,為了利用多核,python開始支援多執行緒。而解決多執行緒之間資料完整性和狀態同步的最簡單方法自然就是加鎖。 於是有了gil這把超級大鎖。

1)按照python社群的想法,作業系統本身的執行緒排程已經非常成熟穩定了,沒有必要自己搞一套。所以python的執行緒就是c語言的乙個pthread,並通過作業系統排程演算法進行排程(例如linux是cfs)。

2)python為了讓各個執行緒能夠平均利用cpu時間,會計算當前已執行的微**數量,達到一定閾值後就強制釋放gil。而這時也會觸發一次作業系統的執行緒排程(當然是否真正進行上下文切換由作業系統自主決定,也就是說真正執行緒排程還是由作業系統來自主決定。那麼也會出現排程到另乙個執行緒,但是還沒拿到gil鎖,因為第乙個執行緒還沒釋放,然後就只能等待)。

3)任何python執行緒執行前,必須先獲得gil鎖,然後,每執行100條位元組碼,直譯器就自動釋放gil鎖,讓別的執行緒有機會執行。這個gil全域性鎖實際上把所有執行緒的執行**都給上了鎖,所以,多執行緒在python中只能交替執行,即使100個執行緒跑在100核cpu上,也只能用到1個核。

4)這種模式在只有乙個cpu核心的情況下毫無問題。任何乙個執行緒被喚起時都能成功獲得到gil(因為只有釋放了gil才會引發執行緒排程)。但當cpu有多個核心的時候,問題就來了。假設有乙個核心上的執行緒一直拿gil鎖執行計算,然後釋放gil,然後又拿鎖,之間幾乎是沒有間隙的放鎖拿鎖執行,所以當其他在其他核心上的執行緒被喚醒時,大部分情況下主線程已經又再一次獲取到gil了。這個時候被喚醒執行的執行緒只能白白的浪費cpu時間(又沒搶到鎖,浪費喚醒執行你的時間),看著另乙個執行緒拿著gil歡快的執行著。然後達到切換時間後進入待排程狀態,再被喚醒,再拿不到鎖等待,以此往復惡性迴圈,

ps:多核cpu情況下(只有乙個cpu核心的情況下毫無問題),這樣感覺就像單執行緒執行一樣,反而比單執行緒還多了喚醒排程另乙個執行緒的步驟;

#

! /usr/bin/python

from threading import

thread

import

time

defmy_counter():

i =0

for _ in range(100000000):

i = i + 1

return

true

defmain():

thread_array ={}

start_time =time.time()

for tid in range(2):

t = thread(target=my_counter)

t.start()

t.join()

end_time =time.time()

print("

total time: {}

".format(end_time -start_time))

if__name__ == '

__main__':

main()

#

! /usr/bin/python

from threading import

thread

import

time

defmy_counter():

i =0

for _ in range(100000000):

i = i + 1

return

true

defmain():

thread_array ={}

start_time =time.time()

for tid in range(2):

t = thread(target=my_counter)

t.start()

thread_array[tid] =t

for i in range(2):

thread_array[i].join()

end_time =time.time()

print("

total time: {}

".format(end_time -start_time))

if__name__ == '

__main__':

main()

下圖就是測試結果

可以看到python在多執行緒的情況下居然比單執行緒整整慢了45%

python中有兩種多工處理:

1. 協同式多工處理:乙個執行緒無論何時開始睡眠或等待網路 i/o,就會釋放gil鎖

2. 搶占式多工處理:如果乙個執行緒不間斷地在 python 2 中執行 1000 位元組碼指令,或者不間斷地在 python 3 執行15 毫秒,那麼它便會放棄 gil,而其他執行緒可以執行

這樣解釋之後感覺gil沒啥影響啊,反正會切換的嘛,那為什麼都說由於gil的存在,導致python的多執行緒比單執行緒還慢,按我的理解,在單核cpu下沒什麼不一樣(也有可能有效能損失),但是在多核cpu下問題就大了,不同核心上的執行緒同一時刻也只能執行乙個,所以不能夠利用多核cpu的優勢,反而在不同核心間切換時會造成資源浪費,反而比單核cpu更慢。

multiprocessing庫的出現很大程度上是為了彌補thread庫因為gil而低效的缺陷。它完整的複製了一套thread所提供的介面方便遷移。唯一的不同就是它使用了多程序而不是多執行緒。每個程序有自己的獨立的gil,因此也不會出現程序之間的gil爭搶。

當然multiprocessing也不是萬能良藥。它的引入會增加程式實現時執行緒間資料通訊和同步的困難。就拿計數器來舉例子,如果我們要多個執行緒累加同乙個變數,對於thread來說,申明乙個global變數,用thread.lock的context包裹住三行就搞定了。而multiprocessing由於程序之間無法看到對方的資料,只能通過在主線程申明乙個queue,put再get或者用share memory的方法。這個額外的實現成本使得本來就非常痛苦的多執行緒程式編碼,變得更加痛苦了。

1)舉個例子,兩個執行緒分別對乙個全域性變數total進行加和減1000000次,但是結果並不是0!而是每次執行結果都不相同。

造成這個結果的原因是gil事實上是會釋放的,解決這種情況就要加執行緒鎖;

2)假設只有乙個程序,這個程序中有兩個執行緒 thread1,thread2, 要修改共享的資料date, 並且有互斥鎖(執行緒鎖)

1、多執行緒執行,假設thread1獲得gil可以使用cpu,這時thread1獲得 互斥鎖lock,thread1可以改date資料(但並

沒有開始修改資料)

2、thread1執行緒在修改date資料前發生了 i/o操作 或者 ticks計數滿100 (注意就是沒有執行到修改data資料),這個

時候 thread1 讓出了gil,gil鎖可以被競爭

3、 thread1 和 thread2 開始競爭 gil (注意:如果thread1是因為 i/o 阻塞 讓出的gil thread2必定拿到gil,如果

thread1是因為ticks計數滿100讓出gil 這個時候 thread1 和 thread2 公平競爭)

4、假設 thread2正好獲得了gil, 執行**去修改共享資料date,由於thread1有互斥鎖lock,所以thread2無法更改共享資料

date,這時thread2讓出gil鎖 , gil鎖再次發生競爭

5、假設thread1又搶到gil,由於其有互斥鎖lock所以其可以繼續修改共享資料data,當thread1修改完資料釋放互斥鎖lock,

thread2在獲得gil與lock後才可對data進行修改

衡量使用:

計算密集型任務由於主要消耗cpu資源,因此,**執行效率至關重要。python這樣的指令碼語言執行效率很低,完全不適合計算密集型任務。對於計算密集型任務,最好用c語言編寫。

io密集型任務(i/o bound)的特點是指磁碟io、網路io佔主要的任務,cpu消耗很少,任務的大部分時間都在等待io操作完成(因為io的速度遠遠低於cpu和記憶體的速度)。

io密集型任務執行期間,99%的時間都花在io上,花在cpu上的時間很少,因此,用執行速度極快的c語言替換用python這樣執行速度極低的指令碼語言,完全無法提公升執行效率。

對於io密集型任務,任務越多,cpu效率越高,但也有乙個限度。常見的大部分任務都是io密集型任務,比如請求網頁、讀寫檔案等。當然我們在python中可以利用sleep達到io密集型任務的目的。

對於io密集型任務,最合適的語言就是開發效率最高(**量最少)的語言,指令碼語言是首選,c語言最差。

參考:最後可以看看多程序+協程

參考:

Python 多執行緒 GIL問題

python 多執行緒gil問題 gil全域性直譯器 由於python直譯器自身設計的需求,只能允許乙個執行緒進行工作,無論你開啟多少個執行緒,python在執行任務的時候在同一時刻只能允許乙個執行緒允許。應為gil的問題,多個執行緒同一時刻只能有乙個執行許可權,多個執行緒會爭取這個執行許可權,造成...

Python多執行緒與GIL

gil global interpreter lock 是在實現python解析器 cpython 時所引入的乙個概念。但值得注意的是,gil並不一定是所有python編譯器均必須的,如jpython就沒有gil。gil是python為解決多執行緒之間資料完整性和狀態同步的辦法,類似於在存在乙個全域...

11 1 多執行緒的GIL

gil global interpreter lock 基於cpython寫的 gil使得python在多核cpu上也只能執行乙個程序 所謂多核多個程序再跑是乙個假象,他是來回切換的,問題 gil 在同乙個程序直到結束才會釋放嗎?total 0 defadd global total for i i...