Python多线程为何比单线程慢?真相竟是GIL
1. 引言一个让无数 Python 开发者困惑的问题很多刚接触 Python 并发编程的朋友都会遇到一个奇怪的现象明明电脑有 8 核 16 线程自己写的多线程程序却跑得比单线程还慢。折腾半天最后在搜索引擎里找到了一个名词——GIL。今天这篇文章我想结合自己的踩坑经历把 GIL 这件事彻底讲清楚。2. 什么是 GIL先看一段代码先来看一段最直观的对比代码。下面这个例子用多线程去执行一个纯 CPU 计算任务看看实际效果import threading import time def cpu_task(): total 0 for i in range(50000000): total i return total 单线程执行 start time.time() cpu_task() print(f单线程耗时: {time.time() - start:.2f}s) 多线程执行 threads [threading.Thread(targetcpu_task) for _ in range(4)] start time.time() for t in threads: t.start() for t in threads: t.join() print(f4线程耗时: {time.time() - start:.2f}s)跑完你会发现单线程耗时 2 秒左右4 个线程跑完反而要 8 秒多。这不是代码写错了而是 GIL 在背后“捣乱”。3. GIL 到底是什么GIL 的全称是 Global Interpreter Lock也就是全局解释器锁。它是 CPython 解释器也就是我们平时用的官方 Python里的一把大锁规定同一时刻只能有一个线程执行 Python 字节码。换句话说不管你的机器有多少个 CPU 核心Python 解释器一次只允许一个线程在跑。其他线程想执行代码可以先排队等锁。这里有个容易混淆的点GIL 锁的是解释器不是整个进程。所以多线程在遇到 I/O 操作比如读写文件、网络请求时线程会主动释放 GIL让其他线程有机会执行。这也是为什么网络爬虫、文件下载这类 I/O 密集型的任务用多线程依然有效。4. 为什么 Python 要设计 GIL很多人会问既然 GIL 影响性能当初为什么还要这么设计这得从 Python 的历史说起。Python 诞生于 1991 年那时候计算机大多还是单核 CPU。GIL 最大的好处是让内存管理变得简单——因为同一时刻只有一个线程在操作对象所以引用计数Reference Counting不需要加额外的锁来保护既避免了死锁风险也大幅降低了实现复杂度。另外很多 C 扩展库比如早期的 NumPy、OpenCV 的某些模块都依赖 GIL 来保证线程安全。如果贸然去掉 GIL这些库的兼容性会出大问题。这也是 GIL 一直“想摘又摘不掉”的根本原因。5. GIL 对多线程性能的影响有多大为了更直观地说明问题我画了一张简单的示意图展示 GIL 下多线程的执行过程flowchart TD A[线程1 获取 GIL] -- B[执行字节码] B -- C{是否遇到 I/O 或时间片用完?} C --|是| D[释放 GIL] D -- E[线程2 获取 GIL] E -- F[执行字节码] F -- G{是否遇到 I/O 或时间片用完?} G --|是| H[释放 GIL] H -- A可以看到线程之间频繁地“抢锁、释放、再抢锁”这个切换过程本身就有开销。当任务本身是纯计算时线程切换的代价甚至超过了并行带来的收益结果就是多线程反而更慢。6. 如何绕过 GIL 的限制既然多线程在 CPU 密集型任务上帮不上忙那有没有别的办法当然有而且不止一种。方案一使用多进程。每个进程都有自己独立的解释器和 GIL所以多进程可以真正利用多核 CPU。代价是进程间通信IPC比线程间共享内存要麻烦一些。from multiprocessing import Pool def cpu_task(n): total 0 for i in range(n): total i return total if name main: with Pool(4) as pool: results pool.map(cpu_task, [50000000] * 4) print(results)方案二使用协程。协程是单线程内的调度不存在 GIL 竞争问题特别适合 I/O 密集型任务。比如 asyncio 就是 Python 官方推荐的异步方案。方案三把计算密集的部分交给 C 扩展。像 NumPy 这类库底层用 C 语言实现在执行矩阵运算时会释放 GIL所以用 NumPy 做科学计算并不会被 GIL 卡住。7. 总结GIL 是 CPython 为了简化内存管理而设计的一把全局锁它让多线程在 CPU 密集型任务上无法利用多核优势。理解 GIL 不是为了抱怨它而是为了在写并发代码时选对工具I/O 密集用多线程或协程CPU 密集用多进程或 C 扩展。最后留一个问题给大家思考Python 3.13 引入了 free-threaded 模式不带 GIL 的构建你觉得这会是 Python 并发编程的转折点吗欢迎在评论区聊聊你的看法。