GIL
由於 python 解釋器(Cpython interpreter)不是線程安全(thread-safe)的,所以 Cpython interpreter 的實現中使用了GIL(global interpreter lock)來阻止多線程同時在一個 pyobject 上操作。這裏所說的 “不是線程安全“ 是指Cpython interpreter在內存管理上不是線程安全的。比如,兩個線程同時增加一個 python object 的引用計數(內存管理用到),如果沒有GIL,那麼這個 python object 的 reference count 只會增加一次。因此,需要有這麼一個規則:只有獲得GIL的線程才能在 python object 上操作或者調用 python/c API 函數。
但是這麼以來,貌似在interpreter執行的python程序只能單線程串行執行了麼?不是,interpreter 爲了模擬併發執行(concurrency of execution),會去嘗試切換線程(詳情可參考sys.setswitchinterval()), 鎖也會在當前線程block時候(例如讀寫文件等阻塞io情況下)釋放掉,以便讓給其他線程。interpreter 也會在執行夠一定數量(通常是100)的 opcode 後釋放鎖。
例子
import threading
import time
total = 0
lock = threading.Lock()
def increment_n_times(n):
global total
for i in range(n):
total += 1
def safe_increment_n_times(n):
global total
for i in range(n):
lock.acquire()
total += 1
lock.release()
def increment_in_x_threads(x, func, n):
threads = [threading.Thread(target=func, args=(n,)) for i in range(x)]
global total
total = 0
begin = time.time()
for thread in threads:
thread.start()
for thread in threads:
thread.join()
print('finished in {}s.\ntotal: {}\nexpected: {}\ndifference: {} ({} %)'
.format(time.time()-begin, total, n*x, n*x-total, 100-total/n/x*100))
reference
https://wiki.python.org/moin/GlobalInterpreterLock
https://docs.python.org/3/c-api/init.html#thread-state-and-the-global-interpreter-lock
https://stackoverflow.com/questions/40072873/why-do-we-need-locks-for-threads-if-we-have-gil