GIL使Python多线程在CPU密集型任务中无法利用多核,仅IO阻塞时能并发;multiprocessing可绕过但开销大;C扩展(如NumPy)自动释放GIL;GIL是CPython兼容性取舍,非bug。

Python的GIL不会“影响”多核性能——它直接让多核在纯Python计算中失效。 你写10个threading.Thread去跑矩阵乘法,监控top或htop会发现:CPU使用率卡死在100%(单核满载),其余核心几乎闲置。这不是配置问题,是CPython解释器的硬性限制。
为什么GIL让threading在CPU密集场景完全无效
GIL本质是一把全局互斥锁,强制所有Python字节码串行执行。哪怕你有32核CPU,threading启动32个线程,它们仍得排队抢同一把锁才能执行Python代码。
- 每次线程执行约100条字节码后,CPython自动释放GIL并尝试切换——但这不是并行,是快速轮询,上下文切换反而增加开销
- 你在
for i in range(10**7)里做纯算术?GIL全程不放,其他线程干等 - 用
dis.dis()反编译函数能看到大量LOAD_FAST、BINARY_ADD等字节码指令,每一条都在GIL保护下执行
threading什么时候能用?只看IO是否阻塞
GIL在遇到系统调用(如read()、recv()、time.sleep())时会主动释放,此时其他线程可立即抢到锁继续执行。这是唯一能发挥threading价值的场景。
- 典型有效用例:
requests.get()发HTTP请求、open().read()读大文件、socket.accept()收连接 - 错误直觉:“我加了
time.sleep(0.1),线程就能并发?”——错。sleep是显式让出GIL,但若主线程没IO操作,只是空循环+sleep,仍是单核打满 - 验证方法:用
strace -f -e trace=epoll_wait,read,write python script.py观察是否真触发系统调用
multiprocessing绕过GIL的代价和边界
每个multiprocessing.Process拥有独立解释器和内存空间,自然不受GIL制约——但进程创建/通信成本远高于线程。
立即学习“Python免费学习笔记(深入)”;
- 数据传递必须序列化:
Queue、Pipe、Manager底层走pickle,传大数组比共享内存慢一个数量级 - 频繁跨进程调用函数(如
Pool.map()处理小任务)可能比单线程还慢,因为fork+序列化开销压倒计算收益 - 真正受益的场景:任务粒度大、计算时间远超IPC开销,例如单次运行>100ms的数值运算
别忽略C扩展和替代解释器的实际效果
NumPy、Pandas、cv2等库内部用C实现,调用时自动释放GIL;PyPy虽仍有GIL但JIT优化大幅降低其影响;Jython无GIL但生态断层严重。
- 检查C扩展是否释放GIL:
grep -r "Py_BEGIN_ALLOW_THREADS" /path/to/site-packages/numpy/ - 用
sys._current_frames()查各线程状态,若大量线程卡在select或epoll_wait,说明GIL已释放,正在真并发等待IO - CPython 3.13起实验性支持
--disable-gil构建,但需重编译且现有C扩展大概率崩溃——目前仅适合科研验证,勿用于生产
最常被忽略的一点:GIL不是bug,是CPython为兼容海量现存C扩展和引用计数内存模型做的取舍。想突破它,要么切进程,要么交出Python字节码控制权(进C/Cython/NumPy),要么换解释器——不存在“关掉GIL就变快”的银弹。


















