GIL 全局解释器锁
约 1764 字大约 6 分钟
2026-05-10
GIL(Global Interpreter Lock,全局解释器锁)是理解 CPython 并发性能时绕不开的概念。
- 什么是 GIL
- 为什么有 GIL
- GIL 对多线程的影响
- 为什么 IO 多线程仍然有用
- 1用 8 个线程跑大循环计算,看耗时是不是接近单线程 —— 体会 GIL 让多线程 CPU 任务“不香”。
- 2同样的循环换成 ProcessPoolExecutor 跑 4 个进程,看 CPU 利用率和总耗时差异。
- 3用 8 个线程并发请求 8 个慢接口(time.sleep 5),看总耗时接近 5 秒 —— IO 等待时 GIL 会释放。
- 4了解:NumPy 大矩阵运算在 C 层会释放 GIL,所以纯 NumPy 的多线程也能用上多核。
- 5Python 3.13+ 已有 free-threading 试验模式(PEP 703),未来部分场景多线程会更划算,但短期内仍按本文结论选型。
GIL(Global Interpreter Lock,全局解释器锁)是理解 CPython 并发性能时绕不开的概念。
什么是 GIL
GIL 是 CPython 解释器中的一把全局锁。它保证同一时刻只有一个线程执行 Python 字节码。
这意味着:即使你创建了多个线程,在 CPU 密集型 Python 代码中,它们通常也不能同时在多个 CPU 核心上执行 Python 字节码。
为什么有 GIL
GIL 的存在和 CPython 的历史实现有关,尤其是内存管理和引用计数。
CPython 使用引用计数管理对象生命周期。对象被引用一次,引用计数加一;引用消失,引用计数减一。如果多个线程同时修改引用计数,就需要大量细粒度锁。GIL 用一把全局锁简化了这个问题。
它的优点:
- 解释器实现相对简单
- 单线程性能较好
- C 扩展开发成本较低
它的缺点:
- CPU 密集型多线程难以利用多核
GIL 对多线程的影响
对于 CPU 密集型任务:
def calculate():
total = 0
for i in range(100_000_000):
total += i
return total多个线程同时执行这类代码时,由于 GIL,同一时刻通常只有一个线程在跑 Python 字节码,所以性能不会随线程数明显提升,甚至可能因为线程切换变慢。
为什么 IO 多线程仍然有用
当线程执行网络请求、文件读写、数据库访问等 IO 操作时,底层会等待外部资源。CPython 通常会在阻塞 IO 期间释放 GIL,让其他线程继续运行。
因此,多线程虽然不适合 CPU 密集型任务,但仍然适合 IO 密集型任务。
如何绕过 GIL
常见方式:
- 使用多进程:每个进程有自己的 Python 解释器和 GIL。
- 使用 NumPy 等 C 扩展:某些 C 扩展在计算时会释放 GIL。
- 把 CPU 密集型代码写成 C/C++/Rust 扩展。
- 使用其他 Python 实现或未来的 free-threading 版本。
多进程绕过 GIL
from concurrent.futures import ProcessPoolExecutor
def calculate(n):
return sum(i * i for i in range(n))
if __name__ == '__main__':
with ProcessPoolExecutor() as executor:
results = executor.map(calculate, [10_000_000] * 4)
print(list(results))不同进程可以运行在不同 CPU 核心上,因此适合 CPU 密集型任务。
GIL 与 asyncio
asyncio 默认是单线程模型。它并不是为了绕过 GIL,而是为了更高效地管理 IO 等待。
如果协程中执行大量 CPU 计算,仍然会阻塞事件循环。
async def bad():
sum(i * i for i in range(100_000_000)) # 阻塞事件循环这种任务应该放到进程池或专门的计算服务中。
free-threading 简介
Python 社区正在推进无 GIL / free-threading 方向。未来某些 Python 版本可能允许在禁用 GIL 的模式下运行,让多线程更好地利用多核。
但在学习和实际项目中,仍然应该以当前主流 CPython 的行为为准:
- IO 密集型:线程或 asyncio
- CPU 密集型:进程或释放 GIL 的计算库
常见误区
- “Python 多线程完全没用”:错。IO 密集型任务中非常有用。
- “asyncio 可以绕过 GIL”:错。asyncio 是单线程协作式并发。
- “有 GIL 就不能并行”:不准确。多进程和释放 GIL 的 C 扩展可以并行。
- “线程越多越快”:错。线程过多会增加调度开销。
GIL 对初学者意味着什么
你不需要一开始就研究 GIL 的内部实现。先记住一个实用结论:
Python 多线程适合 IO 等待,不适合让纯 Python CPU 计算同时跑满多个核心。这也是为什么网络请求、文件 IO 用线程经常有用,而纯计算用线程效果不明显。
一个直观例子
IO 等待任务:
import time
def io_task():
time.sleep(1)线程在 sleep 或等待网络时,会把执行机会让出去,所以多个线程可以交替推进。
CPU 计算任务:
def cpu_task():
total = 0
for i in range(30_000_000):
total += i * i
return total这种任务一直在执行 Python 字节码,GIL 会让同一时刻主要只有一个线程执行 Python 代码,所以多个线程不一定更快。
那线程是不是没用了
不是。线程仍然很有用,尤其是:
- 同时请求多个网站;
- 同时等待多个慢接口;
- 后台执行不太重的任务;
- 调用会释放 GIL 的 C 扩展库;
- 让主程序保持响应。
很多真实项目里,线程池处理 IO 任务仍然简单有效。
CPU 密集任务怎么办
常见选择有:
- 使用
multiprocessing或ProcessPoolExecutor; - 使用 NumPy、Pandas 等底层 C 实现的库;
- 把重计算交给数据库、搜索引擎或专门服务;
- 使用 Cython、Rust、Go 等写性能关键部分;
- 先优化算法,而不是先加并发。
对初学者来说,最先应该掌握的是:CPU 密集任务优先试试多进程,而不是盲目加线程。
不要把 GIL 当成所有慢的原因
程序慢可能有很多原因:
- 算法复杂度太高;
- 数据库查询慢;
- 网络接口慢;
- 文件读写慢;
- 日志太多;
- 并发数量过高导致反而拥塞。
看到 Python 程序慢,不要第一反应就怪 GIL。先用简单计时、日志或 profiling 找瓶颈。
总结
GIL 限制的是 CPython 中多个线程同时执行 Python 字节码的能力。它让 CPU 密集型多线程难以利用多核,但不妨碍多线程提升 IO 密集型任务的吞吐量。理解 GIL,可以帮助我们正确选择线程、进程和 asyncio。
- GIL 的实用结论是:线程适合 IO 等待,不适合加速纯 Python CPU 计算。
- CPU 密集任务优先考虑多进程、底层 C 实现库或算法优化。
- 程序慢不一定是 GIL,先定位瓶颈,再决定是否引入并发。
版权所有
版权归属:Shuo Liu
