GIL
每一个Python线程,在CPython解释器中执行时,都会先锁住自己的线程,阻止别的线程执行。
为什么又GIL
CPython引进GIL主要两个原因
-
设计者为了规避类似于内存管理这样的复杂的竞争风险问题(race condition)
-
二是因为CPython大量使用C语言库,但大部分C语言库都不是原生线程安全的(线程安全会降低性能和增加复杂度)
GIL是如何工作的
如下图,其中Thread 1、2、3轮流执行,每一个线程在开始执行时,都会锁住GIL,以阻止别的线程执行;同样的,每一个线程执行完一段后,会释放GIL,以允许别的线程开始利用资源
CPython还有另一个机制,叫做check_interval,意思是CPython解释器会去轮询检查线程GIL的锁住情况。
每隔一段时间,Python解释器就会强制当前线程去释放GIL,这样别的线程才能有执行的机会。
Python的线程安全
有了GIL,也并不意味Python线程就是安全的了。
import threading
n = 0
def foo():
global n
n += 1
threads = []
for i in range(100):
t = threading.Thread(target=foo)
threads.append(t)
for t in threads:
t.start()
for t in threads:
t.join()
print(n)
执行的时候,大部分时候能够打印100,但也会打印99或这98.
这是因为,n +=1 这一句代码让线程并不安全。翻译foo这个函数的bytecode,可以发现,它实际由下面四行bytecode组成:
>>> import dis
>>> dis.dis(foo)
LOAD_GLOBAL 0 (n)
LOAD_CONST 1 (1)
INPLACE_ADD
STORE_GLOBAL 0 (n)
这四行bytecode中间都是有可能被打断的
注:GIL的设计,主要是为了方便CPython解释器层面的编写者,而不是Python应用层面的程序员。
作为Python的使用者,需要lock等工具,确保线程安全。
n = 0
lock = threading.Lock()
def foo():
global n
with lock:
n += 1