Python 的 GIL 到底卡住了啥?为什么多线程跑不快,3 个实验看清真相
系列:Python 底层设计逻辑|类型:原理拆解型|分发:CSDN / 博客园 / 腾讯云
字数:约 1600 字
结论先行:GIL(全局解释器锁)是 CPython 里一把"全局锁",保证同一时刻只有一个线程在跑 Python 字节码。它锁的是"解释器执行权",不是你的变量。所以 CPU 密集型任务用多线程基本白搭——多个线程轮流抢这把锁,反而有切换开销;但 I/O 密集型任务(网络、文件)不受影响,因为等待时会释放 GIL。想真并行算,得上多进程或把计算丢给 C 扩展。
1. 先破除一个误解:GIL 锁的不是你的数据
很多人以为 GIL 是防止多线程改同一个变量。错。保护变量靠的是你自己的锁(threading.Lock)。GIL 锁的是解释器本身——CPython 的内存管理(引用计数)不是线程安全的,所以干脆让同一时刻只有一个线程能执行字节码,从根本上避开竞争。
import threading
x = 0
def inc():
global x
for _ in range(100000):
x += 1
关键:上例结果不对,是因为 x += 1 不是原子操作,GIL 会在中间切换——这跟 GIL"防竞争"没关系,恰恰说明 GIL 不保护你的数据,你得自己加锁。
2. 实验一:CPU 密集型,多线程 vs 多进程
import time, threading, multiprocessing
def cpu_task():
n = 0
for _ in range(10_000_000):
n += 1
t0 = time.time()
ts = [threading.Thread(target=cpu_task) for _ in range(4)]
[t.start() for t in ts]; [t.join() for t in ts]
print("线程:", time.time() - t0)
t0 = time.time()
ps = [multiprocessing.Process(target=cpu_task) for _ in range(4)]
[p.start() for p in ps]; [p.join() for p in ps]
print("进程:", time.time() - t0)
现象:四核机器上,多进程明显比多线程快接近一倍,多线程几乎没加速。原因就是 GIL——四个线程挤一把锁,谁也别想真并行算。
3. 实验二:I/O 密集型,多线程反而香
import time, threading, requests
def io_task():
requests.get("https://www.baidu.com") # 网络等待
t0 = time.time()
ts = [threading.Thread(target=io_task) for _ in range(10)]
[t.start() for t in ts]; [t.join() for t in ts]
print("IO 线程:", time.time() - t0)
为什么:网络请求时线程在等,CPython 会在等待点释放 GIL,别的线程就能跑。所以 I/O 密集场景多线程完全能打,这也是 Web 服务爱用多线程的原因。
4. 实验三:GIL 在 Python 3 里其实会变脸
Python 3.2 之后 GIL 改用"超时切换":一个线程默认跑 5 毫秒就主动让出,避免单线程霸占。你可以用 sys.setswitchinterval 调这个值观察行为变化。但别指望它让 CPU 任务变快——切换更公平,不等于能并行。
5. 那到底怎么绕开 GIL?
- CPU 密集:用
multiprocessing开多进程,每个进程独立 GIL、独立解释器,真并行。(代价是进程间通信贵。) - 计算下沉 C:NumPy、Pandas 的底层是 C,计算时释放 GIL,所以向量化操作不受 GIL 拖累。
- 异步
asyncio:单线程并发处理海量 I/O,本就不依赖多线程。 - 换解释器:PyPy、Jython 没有 CPython 这种 GIL;官方也在做"nogil"实验分支。
对照表:什么时候 GIL 是事,什么时候不是
| 场景 | 多线程有效吗 | 原因 |
|---|---|---|
| 纯计算(循环/数学) | ❌ 几乎无效 | 抢同一把 GIL,无法并行 |
| 网络/文件 I/O | ✅ 有效 | 等待时释放 GIL |
| NumPy 向量化计算 | ✅ 有效 | 底层 C 释放 GIL |
| 多进程 | ✅ 真并行 | 每进程一把独立 GIL |
排错清单(收藏级)
- "我开了 8 个线程为啥 CPU 只跑满 1 核":CPU 密集任务被 GIL 卡住,改多进程。
- 多线程改同一变量结果错:GIL 不保护你的数据,该加
Lock还得加。 - Web 服务用多线程没问题:因为瓶颈在 I/O,GIL 早释放了。
- 想加速数值计算:优先向量化(NumPy),别盲目堆线程。
- 别神话多进程:进程开销大、不能共享内存,轻量并发还是线程/协程。
一句话记住:GIL 锁的是解释器的"执行权",不是你的数据。CPU 活儿上进程,I/O 活儿随便多线程——先分清瓶颈在哪,再决定怎么绕。

浙公网安备 33010602011771号