Python 3.13 自由线程实战:在无GIL模式下释放多核CPU的真正性能

2026-07-31 0 280

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 压在一个核上相比,是一种全新的编程体验。

Python 3.13 自由线程实战:在无GIL模式下释放多核CPU的真正性能
收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

版权声明:
本站资源有的来自互联网收集整理,本站纯免费分享提供学习使用,如果侵犯了您的合法权益,请联系本站我们会及时删除。
本站资源仅供研究、学习交流之用,免费开源项目不代表完全可商用,若商业用途请先咨询开发企业能否商用,否则产生的一切后果将由下载用户自行承担。
原创板块未经允许不得转载,否则将追究法律责任。

淘吗网 python Python 3.13 自由线程实战:在无GIL模式下释放多核CPU的真正性能 https://www.taomawang.com/server/python/2465.html

常见问题

相关文章

猜你喜欢
发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务