python多线程跑不满多核,根本原因是cpython的gil强制同一时刻仅一个线程执行python字节码;cpu密集型任务因无法释放gil而串行执行,io密集型任务则通过主动释放gil实现伪并行。

因为GIL会让CPU密集型多线程任务实际变慢,而不是变快——这不是bug,是CPython设计的必然结果。
threading.run() 为什么跑不满多核?
GIL不是“锁住线程”,而是锁住整个CPython解释器执行Python字节码的能力。哪怕你用 threading.Thread 启了8个线程,top 或 htop 看到的 CPU 使用率也永远卡在 ~100%,不会接近 800%。这是因为:
- 所有线程都得排队抢同一把
GIL才能执行 Python 代码 - 纯计算循环(比如
sum(range(10**7)))几乎不释放 GIL,线程无法让出控制权 - 线程切换本身有开销,四线程跑计算任务反而比单线程慢 5%–20% 是常见现象
requests.get() 为什么多线程还有效?
IO操作会主动释放 GIL,这是多线程在爬虫、API调用中依然好用的关键:
- 调用
requests.get()、open()、socket.recv()等时,当前线程立刻交出GIL - 其他线程趁机拿到
GIL并执行自己的 IO 请求或少量计算 - 真正耗时的是网络往返,CPU 在等响应时完全空闲,GIL 释放后资源被充分利用
注意:如果在 requests 回调里塞进大量本地解析(如用纯Python处理大JSON),GIL 会重新被占满,拖慢整体吞吐。
multiprocessing.Process vs threading.Thread 怎么选?
选错并发模型是性能问题最常见根源。判断依据不是“要不要并发”,而是“瓶颈在哪”:
- CPU密集型(数值计算、图像处理、加密解密)→ 必须用
multiprocessing.Process,每个进程有独立GIL和内存空间 - IO密集型(HTTP请求、数据库查询、文件读写)→
threading.Thread足够,启动快、内存开销小 - 混合型任务(先请求再计算)→ 拆开:IO部分用
threading,计算部分扔给multiprocessing或concurrent.futures.ProcessPoolExecutor
别直接把 threading 代码改成 multiprocessing —— 共享数据要走 Queue 或 Manager,全局变量不自动同步,序列化开销可能抵消并行收益。
NumPy 运算为什么不受 GIL 拖累?
像 np.dot()、np.sum() 这类操作底层调用的是 C/Fortran 编译代码,并在执行前显式释放 GIL:
- 一旦进入 NumPy 的 C 层,当前线程就不再持有
GIL - 多个线程可同时运行不同数组运算,真正利用多核
- 但 Python 层的循环包装(如
for arr in array_list: np.sum(arr))仍受 GIL 限制,必须用np.stack()+ 一次性向量化操作才高效
这说明:GIL 只管 Python 字节码,不管底层 C 扩展——但前提是扩展作者正确调用了 Py_BEGIN_ALLOW_THREADS / Py_END_ALLOW_THREADS。
真正容易被忽略的点是:GIL 释放时机不是由你写的 Python 逻辑决定的,而是由 CPython 解释器和底层库共同控制。你以为在并发,其实只是在轮流等待;你以为在串行,其实 IO 已经帮你做了并发调度。关键不是绕过 GIL,而是看清任务本质,让工具匹配瓶颈。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











