全局鎖(global interpreter lock,GIL)
討論python的thread 我們就不得不提到GIL
這邊必須先聲明 GIL不是python的特性
這是cpython的產物
cpython是python的直譯器
假如您想避開GIL 您可以考慮其他直譯器 諸如jpython
但cpython是大多數情況的默認環境
所以大多數python使用者還是得面對GIL
GIL創立的目的是為了解決多線程的安全問題
這邊要先介紹cpython的記憶體管理機制
cpython有一種管理方式叫引用記數
cpython的object的struct裡面有個東西叫ob_refcnt
當你創建或引用變數時 ob_refcnt會+1
反之-1
當我們使用多線程時 線程會共用記憶體
這意味著線程會共用全局變數
當不同線程同時修改同個變數的引用記數
這就可能導致內存洩漏或內存提前被釋放
總之 cpython考慮線程安全 才會引進GIL
GIL的具體運作方式
線程執行前 必須取得GIL 取得後才能開始執行任務
如果線程進入IO任務或執行超過一定時間(通常15毫秒)或執行完成
釋放GIL
所以一個線程會經歷三個步驟:
1.爭取GIL
2.執行任務
3.釋放GIL
因為只有一個GIL 所以一段時間內只會有一個線程運行
假設是多核心的情況 其他進程本來能在其他核心運行
因為GIL的緣故 其他核心只能在一旁閒置
所以python的多線程才會被說實際是單線程
從執行的效率來說 有GIL的多線程甚至不如單線程
因為每當GIL釋放 系統需要執行線程切換
線程切換是保存線程的狀態 並載入下一個執行的線程
再者 GIL釋放時 線程會被喚醒競爭GIL
當GIL頻繁被釋出 上述行為重複執行
這耗費了大量的CPU資源
反觀單線程就不需要執行上述行為
所以在CPU密集型任務 單線程優於多線程
PY:
import time
def countdown(n):
while (n > 0):
n = n - 1
start = time.time()
countdown(100000000)
end = time.time()
print('總時間:', end - start)
result:
總時間: 2.3528549671173096
py:
import time
import threading
def countdown(n):
while (n > 0):
n = n - 1
t1 = threading.Thread(target=countdown, args=(100000000/2,))
t2 = threading.Thread(target=countdown, args=(100000000/2,))
start = time.time()
t1.start()
t2.start()
t1.join()
t2.join()
end = time.time()
print('總時間:', end - start)
result:
總時間: 2.818242073059082
上述結果可以證明在cpu密集型任務 單線程優於多線程
先這樣 後面應該會寫gil爭搶的可能問題 gil實際並沒有線程安全
怎解決gil問題ㄅ
累了 剩下明天再寫
參考資料:
https://wiki.python.org/moin/GlobalInterpreterLock
https://yhtechnote.com/global-interpreter-lock/