Python多线程跑CPU密集任务比单线程慢,因GIL强制串行执行字节码;IO密集型任务中多线程仍有效,因阻塞调用会自动释放GIL;真正压满多核应使用multiprocessing。

Python多线程跑CPU密集任务,为什么比单线程还慢?
因为GIL(全局解释器锁)强制让所有线程串行执行Python字节码——哪怕你开了10个线程,它们实际是轮流抢一把锁,不是并行干活。
常见错误现象:threading.Thread 启动一堆计算循环,time.time() 测出来总耗时反而比单线程更长;top里CPU只占满一个核,其他核空转。
- GIL只在纯Python代码执行时生效;一旦调用C扩展(如
numpy.dot、zlib.compress),GIL通常会被释放,此时能真正并行 - CPython默认每执行约100个字节码指令就主动释放一次GIL(可通过
sys.setswitchinterval()调整,但不推荐改) - 不要试图“绕过”GIL——它不是bug,是CPython内存管理(引用计数)的必要保障
IO密集型任务里,多线程为啥还能提效?
因为IO操作(网络请求、文件读写、数据库查询)会触发GIL自动释放,其他线程立刻能抢到锁继续跑。
使用场景:Web爬虫并发请求、日志轮转+写入、API网关批量转发——这些任务90%时间在等响应,不是算东西。
立即学习“Python免费学习笔记(深入)”;
-
requests.get()、open().read()、socket.recv()等阻塞调用都会让出GIL - 用
concurrent.futures.ThreadPoolExecutor比裸写threading更安全,自动管理线程生命周期 - 注意连接池复用(如
requests.Session),避免频繁建连掩盖了GIL带来的并发收益
想真正压满多核?别用threading,改用multiprocessing
每个进程有独立的Python解释器和GIL,天然绕开限制——这是CPython生态下最直接、最可靠的方案。
性能影响:进程启动开销比线程大,内存占用翻倍(数据要序列化传递),但CPU密集任务提速接近线性(4核≈3.8倍快)。
- 优先用
multiprocessing.Pool.map()或concurrent.futures.ProcessPoolExecutor,别手写Process子类 - 传参必须可被
pickle序列化,函数不能是lambda或嵌套定义(会报AttributeError: Can't pickle local object) - 小任务别盲目上多进程——如果单次计算
什么时候该考虑PyPy、Jython或Rust扩展?
PyPy有JIT且GIL行为不同(部分场景可释放更早),但兼容性不如CPython;Jython无GIL但生态萎缩;Rust扩展(如pyo3)能写出无GIL的CPU绑定函数,但开发成本高。
容易踩的坑:PyPy对C扩展支持有限(numpy可用,但很多cv2、torch二进制包直接报错);Jython不支持Python 3.8+;Rust模块要处理好Python对象生命周期,否则引发段错误。
- 先确认瓶颈真在GIL——用
cProfile看是不是卡在built-in method或纯Python循环里 - 优先尝试
numpy/numba向量化或cython编译热点函数,比换解释器或语言更轻量 - 别为了“去掉GIL”而引入新运维复杂度——线上服务稳定性常比理论峰值更重要
真正麻烦的不是GIL本身,而是它藏得太深:你看到threading能跑、top显示多线程、文档说“支持并发”,结果一测性能就掉坑里。盯住任务类型(CPU or IO)、测量真实耗时、再选工具链,比纠结“为什么Python这么设计”实在得多。










