目錄
1.GIL是什么以及影響
2.為什么會有GIL鎖?
1.GIL是什么以及影響
在Python中,多線程的并發性受到全局解釋器鎖(GIL,Global Interpreter Lock)的影響。GIL是CPython(Python的官方實現)中的一個特性,它的主要目的是確保在任何時候只有一個線程在執行Python字節碼。
這是因為CPython的內存管理不是線程安全的,所以需要一個鎖來防止多個線程同時修改或訪問共享的內存區域。
然而,這并不意味著Python多線程不能實現真正的并發。GIL允許線程之間進行切換,但每次只有一個線程能夠持有GIL并執行Python字節碼。當線程需要執行I/O操作或調用某些內置函數(如time.sleep)時,它可能會釋放GIL,允許其他線程獲得執行權。這種切換機制使得Python多線程在某些情況下(如I/O密集型任務)仍然可以實現并發性能的提升。
但是,對于計算密集型任務,Python多線程可能無法充分利用多核CPU的優勢,因為GIL限制了同一時間只有一個線程能夠執行Python字節碼。在這種情況下,使用多進程(multiprocessing)可能是更好的選擇,因為多進程允許每個進程擁有自己的GIL和內存空間,從而實現真正的并行執行。
總之,GIL的作用是確保CPython的內存管理在多線程環境中的線程安全性,但它也限制了Python多線程在計算密集型任務中的并發性能。因此,在選擇使用多線程還是多進程時,需要根據具體的任務類型和性能需求進行權衡。
GIL并不是Python的特性,Python完全可以不依賴于GIL。
2.為什么會有GIL鎖?
原因在于python的內存管理機制采用引用計數機制,引用計數這個變量不是線程安全的,需要用GIL全局鎖來進行數據保護。
python中的對象使用引用計數為主,標記清楚和隔代回收為輔來進行內存管理。所有python腳本中創建的對象,都會配備一個引用計數,來記錄有多少個指針來指向它。以全局變量a為例,每有一個線程若調用了a,則a的引用計數加1。
注意:全局變量的引用計數并不直接與線程數量相關。全局變量(或任何Python對象)的引用計數僅與其被引用的次數有關。比如有10個線程,但只有9個線程引用該變量,則引用計數為9。
當一個線程引用一個全局變量時,該變量的引用計數增加;當引用被刪除(比如線程結束使得引用被刪除)或超出作用域(比如局部變量的結束)時,引用計數減少。另外每個線程可以獨立地引用或取消引用全局變量,從而影響其引用計數。
當對象的引用計數為0時,Python的垃圾回收器會釋放該對象占用的內存。但是,請注意,這并不意味著對象占用的內存會立即返回給操作系統;它可能會被Python的內存管理器保留以供將來使用。
那么為什么需要GIL鎖呢,因為這個引用計數變量不是線程安全的,舉例說明如下:
- 假設開始時只有主線程,引用一個數據(a=100,引用計數x為1),
- 若再開啟2個python子線程,每個線程的開啟都會使得x進行自增1的操作(x+=1),意味著有多個線程對同一個資源的競爭,如果有GIL鎖存在的話,x最后正常會變成3
- 但是如果沒有GIL鎖的話,x可能最終是2。這造成的后果是,當第1個子線程結束時,會把引用計數x減少為1;當第2個線程結束時,會把引用計數x減少為0,這時變量a會被釋放所占用的內存。之后若主線程再次試圖訪問a這個數據時,將會程序異常,會無法找到有效的內存,程序會出錯。
總結GIL存在的目的:GIL的存在是為了防止在多個線程同時執行Python字節碼時,由于內存管理(如引用計數)的并發操作導致的競態條件。GIL確保在任何時候只有一個線程在執行Python字節碼。
end