Python多线程在CPU密集型任务中变慢是因GIL强制串行执行;multiprocessing可解但有序列化开销;NumPy/Cython等绕过解释器路径才是高效方案。

Python多线程在CPU密集型任务中变慢,不是代码写错了,而是CPython解释器强制你“排队计算”——GIL让所有线程抢同一把锁,谁拿到谁算,其余干等。这不是bug,是设计使然。
为什么threading跑计算任务反而更耗时?
因为线程启动、切换、争抢GIL本身就要消耗CPU时间,而纯计算又不触发GIL释放(不像requests.get()或open()会主动让出),结果多个线程在单核上反复上下文切换,实际执行仍是串行的。
- 每100个Python字节码指令后,
GIL才可能轮转一次,但轮转开销远大于收益 - 两个线程做同样总量的计算,耗时通常比单线程多10%–30%,实测常见于
fibonacci()、sum([i**2 for i in range(10**7)])这类场景 -
concurrent.futures.ThreadPoolExecutor包装再多层,也绕不开这个底层限制
multiprocessing真能解决问题吗?
能,但代价明确:进程间不共享内存,每次传参/取结果都要pickle序列化,数据越大越拖慢;启动新进程本身也有毫秒级开销。
- 适合单次计算量大、参数和返回值小的任务(如对10万行做独立数值变换)
- 避免频繁
Process创建——用Pool或ProcessPoolExecutor复用进程 - 注意Windows下子进程会重新导入模块,
if __name__ == '__main__':必须有,否则报RuntimeError: An attempt has been made to start a new process
比multiprocessing更轻量的替代方案有哪些?
不是所有计算都值得上多进程。先看能不能“不动线程模型,只换执行体”:
立即学习“Python免费学习笔记(深入)”;
-
NumPy/SciPy数组运算:底层C/Fortran实现,自动释放GIL,单线程就能吃满多核 -
concurrent.futures.ProcessPoolExecutor+functools.partial:比裸multiprocessing更易管理,支持超时和回调 - Cython或
ctypes封装C函数:在C代码段手动调用PyThreadState_Release和PyThreadState_Swap释放GIL,适合已知热点函数
真正容易被忽略的点:GIL不是“锁住整个程序”,而是“锁住Python字节码执行”。只要计算逻辑能下沉到不经过解释器的层面(比如向量化、C扩展、甚至subprocess.run(['rust-code'])),就自然绕开了它——优化方向不在“怎么开更多线程”,而在“让哪部分代码彻底不走Python解释路径”。


















