对于广大写Python的人来说,GIL(Global Interpreter Lock, 全局解释器锁)肯定不陌生,但未必清楚GIL的历史和全貌是怎样的,今天我们就来梳理一下GIL。
1. 什么是GIL
GIL的全称是 Global Interpreter Lock,全局解释器锁。之所以叫这个名字,是因为Python的执行依赖于解释器。Python最初的设计理念在于,为了解决多线程之间数据完整性和状态同步的问题,设计为在任意时刻只有一个线程在解释器中运行。而当执行多线程程序时,由GIL来控制同一时刻只有一个线程能够运行。即Python中的多线程是表面多线程,也可以理解为fake多线程,不是真正的多线程。
可能有的同学会问,同一时刻只有一个线程能够运行,那么是怎么执行多线程程序的呢?其实原理很简单:解释器的分时复用。即多个线程的代码,轮流被解释器执行,只不过切换的很频繁很快,给人一种多线程“同时”在执行的错觉。聊的学术化一点,其实就是“并发”。
再拓展一点“并发”和“并行”的概念:
普通解释:
并发:交替做不同事情的能力
并行:同时做不同事情的能力
专业术语:
并发:不同的代码块交替执行
并行:不同的代码块同时执行
那么问题来了,Python为什么要如此设计呢?即为什么要保证同一时刻只有一个线程在解释器中运行呢?
答案是为了进程安全。
2. 什么是线程安全?
我们首先要搞清楚什么是进程,什么是线程。进程是系统资源分配的最小单位,线程是程序执行的最小单位。
举一个例子,如果我们把跑程序比作吃饭,那么进程就是摆满了饭菜的桌子,线程就是吃饭的那个人。
在多线程环境中,当各线程不共享数据的时候,那么一定是线程安全的。问题是这种情况并不多见,在多数情况下需要共享数据,这时就需要进行适当的同步控制了。
线程安全一般都涉及到synchronized,就是多线程环境中,共享数据同一时间只能有一个线程来操作 不然中间过程可能会产生不可预制的结果。
接着刚才的例子,桌子上有三碗米饭,一个人正在吃,吃了两碗米饭,但是还没有吃完,因此桌子上米饭的数量还没有更新;此时第二个人也想吃米饭,如果没有线程安全方面的考虑,第二个人要是想直接拿三碗米饭吃,就会出错。
以下是这种情况的代码示例:
n = 0 def foo(): global n n += 1
我们可以看到这个函数用 Python 的标准 dis 模块编译的字节码:
>>> import dis >>> dis.dis(foo) LOAD_GLOBAL 0 (n) LOAD_CONST 1 (1) INPLACE_ADD STORE_GLOBAL 0 (n)
代码的一行中, n += 1,被编译成 4 个字节码,进行 4 个基本操作:
- 将 n 值加载到堆栈上
- 将常数 1 加载到堆栈上
- 将堆栈顶部的两个值相加
- 将总和存储回 n
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)
n = 0 lock = threading.Lock()

