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