GIL 为什么存在?
未标记
考查重点:解释器并发保护与性能代价。GIL 简化 CPython 对象与引用计数的并发保护;代价是传统构建中 CPU 密集 Python 线程难以多核并行
本题目录
参考答案
核心回答
GIL 是 CPython 为保护解释器内部共享状态而采用的一把互斥锁,是实现上的取舍,不是 Python 语言规定所有解释器都必须有的机制。
在 CPython 默认启用 GIL 的构建中,同一解释器内的线程,执行 Python 字节码、访问 Python 对象时通常需要持有 GIL,因此这些线程不能同时在多个核上执行这部分代码。
它的主要作用是让对象引用计数、容器内部结构等操作得到协调,简化解释器和 C 扩展访问 Python 对象时的同步约定。代价是限制同一解释器中纯 Python 计算任务的线程并行能力。但线程仍是真正的操作系统线程,多线程并没有消失。
为什么需要这把锁,为什么不直接删掉?
CPython 的传统对象实现依赖许多共享状态。例如两个线程同时增加同一对象的引用计数,如果没有恰当同步,两次增加可能只留下了一次的结果,进而破坏对象生命周期的判断。容器也要维护长度、元素和引用等内部关系。
GIL 用较粗的互斥范围,避免每处访问都独立处理这些竞争,也让 C 扩展可以遵循“持有 GIL 后操作 Python 对象”的约定。这降低了实现复杂度,但没有让并行计算的需求消失。
因此,去掉 GIL 不等于删除一次加锁调用,还必须重新处理引用计数、内存回收、容器同步和扩展兼容性,并评估单线程开销与多线程扩展能力。自由线程 CPython 正是通过调整这些机制来支持没有 GIL 的执行,而不是取消所有同步。
GIL 对实际任务有什么影响?
- 主要执行
Python代码的 CPU 密集任务:例如多个线程分别做大量整数运算,通常不能靠增加线程数让同一解释器充分利用多核,还可能增加调度与竞争成本。任务可拆分时,可以考虑多进程,同时承担启动、通信和数据传输成本。 - 主要等待 I/O 的任务:例如并发等待多个网络响应,常见阻塞 I/O 路径会释放
GIL,让其他线程继续执行,因此多线程仍可能提高吞吐。效果取决于真正的耗时位置和并发上限。 - 原生扩展中的计算:扩展可以在不访问
Python对象的计算区间释放GIL,所以不能把“同一解释器的Python字节码受限”扩展成“所有 C 扩展计算都只能使用一个核”。是否释放,要看具体实现。
这些是选型依据,不是固定的性能保证。异步 I/O 也可以组织大量等待任务,但不会自动把 CPU 密集的 Python 代码变成多核并行计算。
有 GIL,业务代码还需要锁吗?
需要。GIL 主要保护解释器内部的相应操作,不保证一整段业务流程原子执行。
例如“检查库存是否足够→扣减库存→记录结果”包含多次操作,线程可能在这些操作之间切换,函数调用中也可能发生 I/O 或释放 GIL。需要按共享状态的业务约束使用锁、队列或其他协调机制,不能因为某次简单计数实验恰好没出错,就认定业务不会竞争。
现在的 CPython 仍然一定有 GIL 吗?
不是。CPython 3.13 引入了实验性的自由线程构建,3.14 起进入正式支持但仍可选的阶段;当前 3.15 文档也区分默认启用 GIL 的构建与自由线程构建。
自由线程构建在关闭 GIL 时,允许多个线程并行执行 Python 代码,但不保证所有程序自动加速。还要确认运行的是哪种构建、运行时是否启用 GIL、依赖的扩展是否支持自由线程;导入未声明支持的扩展时,GIL 可能被重新启用。没有 GIL 之后,业务共享数据同样需要正确同步。
参考资料
- 国内:廖雪峰:多线程:采用线程共享状态和锁的解释,说明为什么业务流程仍需同步;其中 GIL 的旧式调度描述不代表当前 CPython。
- 国内:骆昊:Python 中的并发编程之一:采用线程、进程和 GIL 限制的对比,说明“存在多个线程”和“Python 计算能多核并行”是不同问题。
- 国内:骆昊:Python 中的并发编程之二:采用计算任务拆分与多进程的讨论,解释在启用 GIL 的常见场景中,为什么多进程是可考虑的方案,以及它对任务划分的要求。
- 国外:Python:Thread states and the global interpreter lock:采用引用计数竞争、持锁访问 Python 对象和释放 GIL 的规定,解释 GIL 保护的内容及 I/O、原生计算的边界。
- 国外:Python:Python support for free threading:采用构建方式、运行状态、扩展兼容性与线程安全说明,解释当前 CPython 的可选自由线程支持,以及为什么关闭 GIL 仍需要同步。





