Python多线程跑CPU密集任务几乎不提速,因GIL强制同一时刻仅一个线程执行字节码,导致多线程实际串行执行,且有线程切换与GIL争抢开销。

Python 多线程跑 CPU 密集任务为什么几乎不提速
因为 GIL(Global Interpreter Lock)强制同一时刻只有一个线程执行 Python 字节码。即使你开了 8 个 threading.Thread,它们在 CPU 密集场景下仍会排队等待 GIL,实际是串行执行。
典型表现:用多线程计算斐波那契、矩阵乘法或循环累加,耗时几乎等于单线程——甚至更慢(线程切换开销+GIL争抢)。
- 纯 Python 循环、数学运算、字符串处理等都受 GIL 限制
-
time.sleep()、socket.recv()、file.read()等 I/O 操作会主动释放 GIL,此时其他线程可运行 - C 扩展(如
numpy的大部分数组运算)通常在内部释放 GIL,所以多线程调用np.dot可能真正并行
什么时候该用 threading 而不是 multiprocessing
当任务本质是 I/O 密集型,且需要共享内存状态(比如共用一个字典缓存、一个数据库连接池),threading 更轻量、通信无序列化成本。
常见适用场景:
立即学习“Python免费学习笔记(深入)”;
- 并发发起 HTTP 请求(
requests.get期间 GIL 已释放) - 监听多个 socket 连接(
select或poll阻塞时让出 GIL) - 定时轮询文件变化或队列消息(
queue.Queue是线程安全的)
注意:threading 下全局变量可直接读写,但需用 threading.Lock 保护临界区;而 multiprocessing 中进程间默认不共享内存,改用 Manager 或 shared_memory 代价更高。
如何验证当前线程是否持有 GIL
没法直接“读取”GIL 状态,但可通过行为间接判断:在纯计算函数中插入 time.sleep(0),若性能显著下降,说明原代码原本在持续占用 GIL;反之,如果加了 sleep 后总耗时不变,可能本就频繁让出 GIL(比如调用了带释放逻辑的 C 函数)。
触发式加载精美特效企业网站源码使用jquery实现了很多精美的触发式加载特效,网站首页在随着访客的滚动条滚动过程中会出现很多触发式加载的特殊效果,让这个网站的风格瞬间显得非常的高大上,让你的企业品牌在访客心中留下更深的影响。当然,我们在使用jquery特效的同时也要注意程序对搜索引擎的友好型,所以这一点儿作者也有考虑到,已经尽可能的对js和css脚本进行精简和优化,尽可能的加快网站加载速度,同时也
更可靠的方式是用系统工具观察 CPU 利用率:
- 单线程 CPU 密集任务:1 个核心跑满(100%)
- 多线程 CPU 密集任务:仍是 1 个核心跑满,其余核心空闲
- 多线程 I/O 密集任务:多个核心活跃(因线程在等待 I/O 时被调度到不同核)
Linux 下可用 htop 查看 per-thread CPU%,macOS 可用 Activity Monitor 切换到 “Threads” 视图。
绕不开 GIL 时的实用替代方案
真要并行 CPU 工作,multiprocessing 是最直接的选择,但它有启动开销和数据序列化成本。对小任务不划算,对大计算才值得。
其他可行路径:
- 用
concurrent.futures.ProcessPoolExecutor替代ThreadPoolExecutor,接口几乎一致,只需改一行初始化代码 - 把计算密集部分封装成独立脚本,用
subprocess.run启动,避免解释器级耦合 - 换语言:Cython 编译关键循环并显式释放 GIL(用
with nogil:),或用 Rust 写扩展(通过pyo3) - 用异步 I/O(
asyncio)处理高并发网络请求——它不解决 CPU 并行,但比多线程更省内存、更高吞吐
GIL 不是 bug,是 CPython 实现内存管理(引用计数)的取舍。理解它何时生效、何时失效,比试图“干掉它”更重要。很多所谓“GIL 问题”,其实是选错了并发模型。









