python多线程跑cpu密集任务不快,因gil强制单线程执行字节码;应改用multiprocessing或processpoolexecutor实现真正并行。

Python 多线程跑 CPU 密集任务为啥不快
因为 GIL(全局解释器锁)强制同一时刻只有一个线程执行 Python 字节码。哪怕你开了 8 个 threading.Thread,CPU 密集型任务(比如数值计算、循环处理)也几乎不会提速,反而可能因线程切换更慢。
常见错误现象:top 或任务管理器里只看到一个 CPU 核心满载,其余闲置;time.time() 测出多线程比单线程还慢。
- 适用场景:仅适合 I/O 密集型任务(如网络请求、文件读写、数据库查询)——线程在等 I/O 时会自动释放
GIL,其他线程就能抢到执行权 - 不适用场景:纯计算(
sum([i**2 for i in range(10**7)]))、图像处理、加密解密等 - 验证方法:用
dis.dis(your_function)看关键循环是否大量调用 Python 解释器操作(如BINARY_ADD、STORE_FAST),这类代码基本绕不开GIL
想真正并行跑 CPU 任务,该用啥
绕过 GIL 的唯一可靠办法是换进程 —— multiprocessing 模块每个子进程有独立的 Python 解释器和 GIL,天然支持多核并行。
但别直接照搬 threading 写法:传参、共享状态、启动开销都不同。
立即学习“Python免费学习笔记(深入)”;
- 用
multiprocessing.Process或multiprocessing.Pool,不是threading.Thread - 函数必须能被序列化(不能是嵌套函数、lambda、类实例方法,除非用
functools.partial包装) - 进程间通信开销大:避免频繁用
multiprocessing.Queue或multiprocessing.Manager传大数据;优先用multiprocessing.Array/multiprocessing.Value做共享内存 - Windows 下注意:必须包在
if __name__ == "__main__":里,否则会反复 fork 子进程
concurrent.futures 是不是更简单
是,但它只是封装,底层仍是 threading 或 multiprocessing。选错执行器,照样白忙活。
错误用法:ThreadPoolExecutor 跑 cpu_bound_func → 还是被 GIL 卡死。
- CPU 密集任务:必须用
ProcessPoolExecutor - I/O 密集任务:用
ThreadPoolExecutor更轻量,启动快、内存占用小 - 注意
max_workers设置:对 CPU 任务,设成os.cpu_count()左右较合理;对 I/O 任务可设更大(如 20–50),但太多会引发系统级资源争抢 - 返回值是
Future对象,别忘了调用.result()获取结果,否则可能卡住不报错
哪些“看起来像绕过 GIL”的操作其实没用
很多开发者试过这些,结果发现性能没改善——根本原因在于它们仍运行在主线程解释器内,GIL 没松动。
- 用
asyncio+await:纯协程不等于多线程,它还是单线程事件循环,CPU 密集任务照样阻塞整个 loop - 调用 C 扩展但没主动释放
GIL:比如自己写的ctypes或cffi函数,若没在 C 侧调用Py_BEGIN_ALLOW_THREADS,Python 层依然持锁 - 用
numpy做矩阵运算:大部分底层是优化过的 C/Fortran,且在关键路径上主动释放了GIL,所以有效;但如果你在外层套了个 Pythonfor循环逐行调用numpy.dot,那循环本身仍受GIL约束
真正要确认是否绕开了 GIL,最实在的办法是用 htop 观察所有 CPU 核心是否同时跑满,而不是只看代码“有没有用多线程”。











