LOADING
1049 words
5 minutes
什么是 GIL:Python 全局解释器锁与并发误区

GIL 几乎是每个 Python 面试都会问到的点,但大多数答案只停留在”一句话定义”。这篇文章把它讲透:GIL 是什么、为什么 CPython 必须有它、什么时候会被释放,以及一个几乎人人会踩的坑——“有 GIL 就不用加锁”。

一、什么是 GIL

GIL(Global Interpreter Lock,全局解释器锁)是 CPython(官方 Python 解释器)中的一个互斥锁,作用是:同一时刻,只允许一个线程执行 Python 字节码。

  • 为什么需要它:CPython 的内存管理使用引用计数(每个对象记录自己被引用了多少次),而引用计数操作不是线程安全的。如果多个线程同时修改引用计数,会导致内存泄漏或对象被过早释放。GIL 保证同一时刻只有一个线程在跑字节码,从而让引用计数操作保持原子,省去了给每个对象加锁的开销。
  • 直接后果:多线程无法真正并行执行 CPU 密集型任务(哪怕机器有 8 核,纯 Python 计算也只有一个核在干活)。

注意:GIL 是 CPython 的产物。Jython、IronPython 没有 GIL,PyPy 也有类似机制但实现不同。你这里 Python 3.12 + 官方解释器,就是有 GIL 的 CPython。

二、什么时候释放 GIL

GIL 会在以下场景被释放,让其他线程有机会运行:

  1. 执行 I/O 操作时(最常见、也是多线程对 I/O 密集任务有效的原因)
    • 网络请求、文件读写、磁盘操作、time.sleep()、等待锁等——线程阻塞等待 I/O 时会主动释放 GIL,让别的线程去跑。
  2. 时间片轮转强制切换
    • Python 3.2 起引入了间隔切换机制,默认每 5ms(sys.getswitchinterval() 可查,sys.setswitchinterval() 可改)强制让出一次 GIL,即使线程没做 I/O。
  3. 调用会主动释放 GIL 的 C 扩展
    • 像 numpy 的矩阵运算、pandas 的部分底层计算,内部 C 代码会显式调用 Py_BEGIN_ALLOW_THREADS 释放 GIL,此时可以真正多核并行。

关键理解:纯 Python 的 CPU 密集代码(如一个大 for 循环做计算)几乎全程持有 GIL,只靠时间片切换串行交替执行,所以多线程对 CPU 密集任务基本无加速;真正能利用多核的是多进程(multiprocessing),或释放了 GIL 的 C 扩展。

三、互斥锁(同步锁)和 GIL 的区别

对比维度GIL互斥锁(threading.Lock)
层面解释器内部锁,保护 CPython 自身状态(主要是引用计数)用户代码锁,保护你自定义的共享数据/临界区
谁管理解释器自动加锁/释放,程序员无法控制程序员手动 acquire()/release()(或用 with)
锁定的对象”Python 字节码执行权”这个全局资源某段临界区代码 / 某个共享变量
粒度大,影响所有线程小,只影响竞争同一把锁的线程
能否关闭/绕过不能(CPython 固定存在)可自由创建多把锁、按需使用

最容易踩的坑:有 GIL ≠ 不用加锁

很多人误以为”有 GIL 所以多线程写数据是安全的”,这是错的。GIL 只保证单个字节码指令的原子性,而一行 Python 代码往往对应多条字节码,中途可能发生线程切换。

import threading
counter = 0
def add():
global counter
for _ in range(1000000):
counter += 1 # 这行不是原子操作!对应多条字节码
threads = [threading.Thread(target=add) for _ in range(2)]
for t in threads:
t.start()
for t in threads:
t.join()
print(counter) # 期望 2000000,实际往往小于它(竞态条件)

counter += 1 内部其实是:读 counter → 加 1 → 写回 counter 三步,中间可能被切走,导致两个线程读到了同一个旧值,结果少加。正确做法是用互斥锁保护临界区:

import threading
counter = 0
lock = threading.Lock() # 用户层面的互斥锁,和 GIL 是两回事
def add():
global counter
for _ in range(1000000):
with lock: # with 会自动 acquire / release
counter += 1 # 临界区:加锁保证这段不被并发打断
threads = [threading.Thread(target=add) for _ in range(2)]
for t in threads:
t.start()
for t in threads:
t.join()
print(counter) # 2000000,正确
什么是 GIL:Python 全局解释器锁与并发误区
/posts/2026-8-15/什么是-gil/
Author
Atopos
Published at
2026-08-15
License
CC BY-NC-SA 4.0

Some information may be outdated