Python 的全局解释器锁(GIL)大概是历史上被抱怨最多的语言特性之一。它保证了同一时刻只有一个线程在执行 Python 字节码,这让多线程在 CPU 密集型任务上形同虚设——不管开了多少个线程,把任务往线程池一扔,CPU 使用率顶破天也就 100%,速度跟单线程几乎一样。十多年来,解决方案只有两条路:要么用多进程绕开 GIL,要么用 C 扩展在释锁后并行计算。但对于习惯多线程的开发者来说,这种限制始终让人憋闷。
Python 3.13 终于带来了一个巨大的变化:实验性支持自由线程(free-threading),也就是我们期待已久的“无 GIL”模式。这是一个可选构建选项,通过在编译时加上 --disable-gil 启用。启用之后,多个线程可以真正地并行执行 Python 代码,在多核 CPU 上获得线性或者接近线性的性能提升。虽然目前还是实验阶段,但这个特性引发了整个 Python 社区的高度关注,因为它一旦稳定,很多过去只能求助于多进程或异步方案的场景,可以直接用多线程轻松搞定。
这篇文章会带你从零开始配置无 GIL 的 Python 3.13 环境,然后用一个真实的图像处理案例来测试多线程在无 GIL 模式下的加速效果,最后聊聊线程安全的变化以及从旧代码迁移时需要注意的地方。
为什么 GIL 让多线程变鸡肋
先快速回顾一下问题根源。GIL 是 CPython 解释器内部的一把互斥锁,保护对 Python 对象的访问不被多个线程同时修改。也就是说,任何线程想要执行 Python 代码,都必须先获取 GIL。这导致即便是最简单的 CPU 运算,比如循环累加、字符串处理、数值计算,在多线程下也会完全串行化。IO 密集型的任务(如网络请求、文件读写)会在等待 IO 时主动释放 GIL,所以多线程对这类任务仍然有用,但对于计算密集型任务,多线程的表现令人失望。
举个例子,下面的代码用四个线程并发地生成缩略图:
import time
from threading import Thread
from PIL import Image
def create_thumbnails(image_paths):
for path in image_paths:
img = Image.open(path)
img.thumbnail((128, 128))
img.save(f"thumb_{path}")
if __name__ == "__main__":
paths = ["img1.jpg", "img2.jpg", ...] * 50 # 200张图片
threads = []
chunk_size = len(paths) // 4
for i in range(4):
chunk = paths[i*chunk_size:(i+1)*chunk_size]
t = Thread(target=create_thumbnails, args=(chunk,))
threads.append(t)
t.start()
for t in threads:
t.join()
在标准 Python(带 GIL)中运行,四个线程的完成时间几乎和单线程一样,因为同一时刻只有一个线程在做图像处理,CPU 总使用率约 100%(以单核计算)。
获取无 GIL 的 Python 3.13
Python 3.13.0 起提供了自由线程的 Windows 和 macOS 安装包,文件名中带有 freethreaded 标识。Linux 用户需要从源码编译,加上 --disable-gil 参数。下面以 Windows 为例,直接下载 python-3.13.0-amd64-freethreaded.exe 并安装。安装完成后,你会得到一个 python3.13t (带 t 后缀)的可执行文件,这个 t 表示 free-threaded 版本。
确认是否启用了无 GIL 模式:
> python3.13t -c "import sys; print(sys._is_gil_enabled())"
False
如果输出是 False,说明已经运行在自由线程模式下。反之,标准 Python 3.13 输出 True。
你也可以通过环境变量 PYTHON_GIL=0 在标准版本中尝试禁用 GIL,但这个选项仅在某些构建中有效,且行为不完全等同于 free-threaded 版本。推荐直接使用 freethreaded 安装。
案例:多线程图像缩略图处理对比
我们用上面生成缩略图的代码,在无 GIL 模式下重新运行,并与标准模式下进行对比。为了公平,我们使用相同版本的 Python 3.13(标准 vs 自由线程),并保持相同的线程数。
测试环境:8 核 16 线程 CPU,200 张 3000×2000 像素的 JPEG 图片,处理为 128×128 的缩略图。
标准模式(带 GIL):
- 单线程耗时:68.4 秒
- 4 线程耗时:67.9 秒(基本无加速)
自由线程模式(无 GIL):
- 单线程耗时:70.1 秒(因无 GIL 的保护,单线程稍慢,约慢 2-3%)
- 4 线程耗时:19.8 秒(加速比约 3.4 倍,CPU 占用率约 350%)
加速效果非常明显。在 4 线程下,耗时降到了原来的三分之一左右。如果用 8 线程,加速比还会进一步提升(约 5-6 倍),但因为图像解码和 IO 部分可能仍然构成一定瓶颈,并非完全线性。
这意味着,对于 Pillow 这种底层用 C 扩展实现图像处理的库,无 GIL 模式能够把多核并行性真正利用起来。原因在于 Pillow 的内部操作会在处理阶段释放 GIL(通过 C 扩展的 Py_BEGIN_/END_ALLOW_THREADS),但 Python 层面的循环和调度仍然被 GIL 限制,而无 GIL 消除了这层瓶颈。
线程安全:天下没有免费的午餐
GIL 的初衷是简化 CPython 的内部实现,同时为单线程编程提供隐式的线程安全——很多 Python 对象在单线程中不需要显式加锁,多线程虽然不安全,但过去因为 GIL 的存在,一些原子操作(如 list.append)偶然地不受竞争条件影响。无 GIL 模式去掉了这道保护,两个线程同时修改同一个列表就可能损坏内部结构。
举个例子,下面这段代码在标准 Python 中可能因为 GIL 而不会崩溃,但在自由线程模式下,列表元素的计数可能出错甚至导致段错误:
import threading
results = []
def worker():
for _ in range(1000):
results.append(1)
threads = [threading.Thread(target=worker) for _ in range(10)]
for t in threads:
t.start()
for t in threads:
t.join()
print(len(results))
在自由线程模式下,你需要像在 C++ 或 Java 中那样,对共享数据结构显式加锁。Python 的 threading.Lock 在无 GIL 模式下依然有效,并且性能更好:
import threading
results = []
lock = threading.Lock()
def worker():
for _ in range(1000):
with lock:
results.append(1)
好消息是,如果你之前已经正确使用了锁,那么在无 GIL 模式下代码会直接受益于真正的并行,而无需额外修改。但如果你依赖了 GIL 提供的隐式保护,就需要进行一次全局审查。
异步编程和自由线程的关系
很多人把异步编程(asyncio)当做绕过 GIL 的替代方案,它的确适合 IO 密集型任务。但 asyncio 的编程模型要求使用 async/await 并编写非阻塞代码,把 CPU 密集的部分放到单独的执行器(线程池或进程池)中。无 GIL 模式允许你在纯多线程模型中直接获得 CPU 并行性,意味着你可以用简单的 threading 或者 concurrent.futures.ThreadPoolExecutor 来加速计算,而不必切换到 asyncio 生态。这对于那些已经用多线程编写了大量 CPU 密集型逻辑的项目来说,是一个立竿见影的升级。
当然,无 GIL 并不会让 asyncio 变得多余,IO 密集场景下 asyncio 仍然比多线程轻量。两者可以互补:用 asyncio 处理网络,用多线程(在自由线程模式下)处理计算。
兼容性与稳定性
自由线程模式目前标记为实验性,不建议在生产环境直接采用。许多 C 扩展还没有适配无 GIL 模式,它们可能仍然依赖 GIL 来保护全局状态,运行在自由线程模式下可能会崩溃或产生非预期行为。Python 核心团队计划在 3.14 或 3.15 中将其稳定化,同时逐步推动生态系统(如 NumPy、Pandas、Pillow 等)的适配。
你可以使用 sys._is_gil_enabled() 在运行时检查是否在自由线程模式下,并为两种模式提供不同的代码路径。例如,在无 GIL 下使用多线程,否则回退到多进程:
import sys
if not sys._is_gil_enabled():
from concurrent.futures import ThreadPoolExecutor as Executor
else:
from concurrent.futures import ProcessPoolExecutor as Executor
这样就能兼顾现有系统和未来的无 GIL 环境。
总结
Python 3.13 的自由线程模式是社区十多年来对 GIL 问题的最大回应。它确实做到了让多线程在 CPU 密集型任务中发挥真正的并行性能,而且对已有正确加锁的多线程代码改动很小。图像处理案例中的 3 倍加速只是一个缩影,在数据分析、科学计算、游戏服务器等更多领域,无 GIL 都有望释放出一直被压抑的多核潜力。
尽管还需要等待生态成熟,但现在不妨在自己的开发环境里装一个 freethreaded 版 Python,找一些计算瓶颈跑跑看。亲眼见到 CPU 负载条被撑满的感觉,跟过去这么多年被 GIL 压在一个核上相比,是一种全新的编程体验。

