page contents

告别 GIL?Python 3.14+ 自由线程实测:多线程真的起飞了吗

你有没有过这种经历:写了个 ThreadPoolExecutor,把线程开到和 CPU 核数一样多,信心满满地跑一个纯 Python 的 CPU 密集任务,结果耗时和单线程几乎没区别?

attachments-2026-10-PBObplkI6ae2a427f3c7c.png

你有没有过这种经历:写了个 ThreadPoolExecutor,把线程开到和 CPU 核数一样多,信心满满地跑一个纯 Python 的 CPU 密集任务,结果耗时和单线程几乎没区别?

不是你代码写错了,是 GIL 在"捣乱"。

GIL(Global Interpreter Lock,全局解释器锁)是 CPython 里一把互斥锁,它保证同一时刻只有一个线程能执行 Python 字节码。换句话说,哪怕你八核、十六核全开,CPU 密集的多线程 Python 代码也只能"排队走单行道"——多核并行的红利,吃不到。

这个老痛点,已经跟了 Python 近 30 年。但到了 Python 3.14,情况真的变了:自由线程(Free-Threading,即无 GIL)构建正式进入"官方支持"阶段。

那么问题来了:多线程 Python,这次是真的起飞了吗?还是又一次"雷声大、雨点小"?

GIL:30 年的老痛点

先说清楚 GIL 到底卡住了什么。

CPython 的内存管理不是线程安全的。为了让解释器在多线程下不崩,早期设计者(以及后续长期的历史包袱)选择了一把全局大锁:任何线程要执行 Python 字节码,先得拿到 GIL。

这带来一个直接后果:CPU 密集型任务无法靠多线程利用多核。

• I/O 密集场景(网络请求、文件读写)下,线程在等待时会释放 GIL,多线程依然香;

• 但一旦进入纯 Python 的运算(比如循环算质数、处理大列表),线程们就在 GIL 前"轮流刷卡",同一时刻只有一个在跑,多核成了摆设。

所以长期以来,Python 想榨干多核,唯一的"正统答案"是 multiprocessing(多进程)——用进程绕开 GIL。但进程有进程的成本:启动慢、内存占用高、跨进程传参要序列化(pickle)。轻量并行,被 GIL 硬生生逼成了重活。

自由线程是什么

自由线程,说白了就是编译时关掉 GIL 的 CPython。构建出来的解释器不再用那把全局大锁保护字节码执行,多个线程可以真正同时在多个 CPU 核上并行跑 Python 代码。

它带来的最大改变,正是上面那个痛点:

在自由线程构建下,ThreadPoolExecutor 跑 CPU 密集的纯 Python 任务,第一次有了"真并行加速"的可能——不再需要为了绕开 GIL 去搬出 ProcessPoolExecutor。

注意两个限定词:

1. 它是"构建开关",不是运行时默认行为。普通 python3.14 仍然带 GIL。

2. 它需要整个生态(尤其是 C 扩展)配合,否则会"退回去"。这一点后面讲坑的时候再说。

自由线程路线图

自由线程不是一夜之间冒出来的,它走的是一条被明确定义的"三阶段路线",由两个关键 PEP 推动。

PEP 703(作者 Sam Gross,Meta,2023-10-24 被指导委员会接受)是"总纲":它提出 --disable-gil 构建开关,正式把"无 GIL 的 CPython"立项。分三阶段推进:

• Phase I:在 3.13 以"实验性(experimental)"提供;

• Phase II:在 3.14 成为"官方支持,但默认仍关闭";

• Phase III:未来某版本,成为默认构建。

PEP 779("Criteria for supported status for free-threaded Python",作者 Thomas Wouters / Matt Page / Sam Gross,状态 Final,2025-06-16 接受,目标版本 3.14)则是"毕业考试"——它定义了进入 Phase II(即"官方支持")必须满足的硬性标准:

• 单线程性能回退 ≤ 15%(以 pyperformance 衡量;3.14 实际已收窄到约 5–10%);

• 内存开销 ≤ 20%(实测约 15–20%);

• C API 稳定;

• 生态有足够的采用度。

换句话说,PEP 703 画了路线图,PEP 779 是验收清单。两个条件都坐实,3.14 的自由线程才敢说"我们官方支持了"。

从实验到官方支持

把两个 PEP 落到版本上,就是下面这条演进线:

• Python 3.13(2024-10):自由线程构建可用,但标注为"实验性"。

• 二进制名:python3.13t

• 出于安全考虑,3.13t 关闭了 specializing adaptive interpreter(专门化自适应解释器),单线程开销一度高达约 40%。

• Python 3.14(2025-10):自由线程成为"官方支持",但仍是可选项。

• 二进制名:python3.14t

• 关键优化:3.14t 重新启用了 specializing adaptive interpreter(3.13t 为安全起见关掉了它)。正是这一步,把单线程开销从约 40% 大幅收窄到约 5–10%,让自由线程从"能用但肉疼"变成"日常可接受的代价"。

• 普通 python3.14 仍然带 GIL,默认行为不变。

一句话总结:3.13 是"给你试",3.14 是"给你用",但用不用、怎么用,主动权还在你手里。

三步上手自由线程

心动了?上手其实不复杂,三步就能跑起来。

第一步:装一个 t 构建

• 官方安装器(python.org 的 3.14 installer)勾选"Download free-threaded binaries";

• 用 uv 一行搞定:uv python install 3.14t;

• 或从源码编译时加 --disable-gil。

装好后,你会多一个 python3.14t 可执行文件。

第二步:确认你真的在用自由线程

三种检测方式,各管一个层面:

import sys, sysconfig

# 1) 构建是否支持(编译时是否 --disable-gil)
sysconfig.get_config_var("Py_GIL_DISABLED") == 1

# 2) 运行时 GIL 是否真的关了(False 表示已关闭)
sys._is_gil_enabled()

# 3) 命令行一行看构建类型
# python -VV  会打印 "free-threading build"

前两个的区别很关键:构建支持 ≠ 运行时一定关闭(因为运行时还能被强制开关,见下一步)。

第三步:强制开关 GIL(可选)

即便在 t 构建里,你也能用环境变量或 -X 标志强制开/关:

• PYTHON_GIL=0 或 python3.14t -X gil=0:强制关 GIL;

• PYTHON_GIL=1 或 python3.14t -X gil=1:强制开 GIL(即使在 t 构建里也能"反悔"回去)。

这套开关在排查性能问题、复现 bug 时特别好用。

实测:并发基准对比

光说不练假把式。下面这段基准,用 concurrent.futures 的 ThreadPoolExecutor 和 ProcessPoolExecutor,在同一份代码下对比"单线程 / 4 线程 / 4 进程"跑一个纯 Python 的 CPU 密集任务(统计质数个数)。

要求:Python 3.14+,请用同一份代码、分别在 python3.14 与 python3.14t 下各跑一遍,对比才有意义。

# 环境:Python 3.14+(分别用 python3.14 与 python3.14t 运行对比)
import sys, time
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor

def is_prime_count(n: int) -> int:
    """纯 Python CPU 密集任务:统计 [2, n) 中的质数个数。"""
    count = 0
    for i in range(2, n):
        prime = True
        for j in range(2, int(i ** 0.5) + 1):
            if i % j == 0:
                prime = False
                break
        count += 1if prime else0
    return count

def bench(make_executor, n: int, tasks: int):
    start = time.perf_counter()
    with make_executor() as ex:
        list(ex.map(is_prime_count, [n] * tasks))
    return time.perf_counter() - start

if __name__ == "__main__":
    N, TASKS = 8000, 4
    print("Python:", sys.version.split()[0], "| GIL:", sys._is_gil_enabled())
    print(f"单线程   : {bench(lambda: ThreadPoolExecutor(1), N, TASKS):.2f}s")
    print(f"4线程    : {bench(lambda: ThreadPoolExecutor(4), N, TASKS):.2f}s")
    print(f"4进程    : {bench(lambda: ProcessPoolExecutor(4), N, TASKS):.2f}s")

怎么读这段代码:

• is_prime_count 是故意写得很"纯 Python"的 CPU 密集函数——双重循环、不调任何会释放 GIL 的 C 扩展,确保瓶颈卡在 Python 字节码上。

• bench 用 ex.map 把同一个任务复制 TASKS=4 份,分别交给"1 线程池 / 4 线程池 / 4 进程池"。

• ThreadPoolExecutor(1) 等效于"伪单线程",用来当基准参照。

典型结果(具体数值随机器、核数、负载而异,此处为定性结论):

• 在带 GIL 的 python3.14 下跑:

• "4 线程"那行 ≈ "单线程"那行耗时(几乎持平)。

• 原因:GIL 让 4 个线程轮流执行,ThreadPoolExecutor 在 CPU 密集上完全没拿到并行加速。

• 在自由线程的 python3.14t 下跑:

• "4 线程"那行 ≈ "单线程"那行的 1/3 耗时,即约 3x 加速(4 核上通常可达约 3–3.5x,接近理论并行上限)。

• 这是 ThreadPoolExecutor 在纯 Python CPU 密集任务上第一次获得真正的多核并行。

为什么这件事重要?
它意味着:对那些"多线程更自然、但被 GIL 逼着用多进程"的场景,你现在可以省掉 ProcessPoolExecutor 的进程开销、内存翻倍,以及参数必须可 pickle 的约束——一个 ThreadPoolExecutor(4) 就够了。

补充一句:如果你把上面任务换成 NumPy 矩阵运算(它本身会释放 GIL),或者换成 I/O 密集任务,那么在 GIL 构建下 4 线程本来就不慢,自由线程下也就"几乎无变化"。自由线程解决的是"纯 Python CPU 密集 + 多线程"这一特定痛点,不是万能加速器。

何时真的香

总结一下,自由线程最香的画像:

CPU 密集、且是纯 Python 逻辑:循环、解析、规则计算,瓶颈在字节码;

任务天然可并行:彼此独立、互不依赖,能直接 map 拆开;

多核机器:核越多,并行收益越明显;

不想承担多进程成本:不想 pickle 参数、不想为每个进程付内存、不想写 if __name__ == "__main__" 的进程入口。

在这些条件下,python3.14t + ThreadPoolExecutor 是一次实打实的升级。

何时是坑(重点)

自由线程不是银弹,下面是踩坑高发区,尤其是第一个,最阴险:

1. "静默重新启用 GIL"陷阱(最坑)
如果你 import 了一个未声明自由线程支持的 C 扩展(没设置 Py_mod_gil = Py_MOD_GIL_NOT_USED),CPython 会自动、且静默地为整个进程重新启用 GIL——默认不报错、不警告。你以为自己在用自由线程,其实 GIL 早就在背后悄悄回来了。
想看到提示,得主动加:-W default::RuntimeWarning。
排查口诀:性能没提升时,先 python3.14t -W default::RuntimeWarning your_script.py 看有没有 GIL 被重新启用的警告。

2. 单线程代码会变慢
自由线程构建下,单线程代码通常慢约 5–10%(引用计数等需要更重的线程安全处理)。如果你的程序本来就是单线程跑,上 t 构建纯属倒贴。

3. 内存开销上涨
通常多约 15–20% 内存。容器、嵌入式等内存敏感场景要算这笔账。

4. 被 GIL 掩盖的线程安全 bug 会暴露
以前靠 GIL 串行"碰巧没出事"的共享可变状态(共享 list、dict 被多线程改),现在真并行了,竞态条件立刻现形。该加锁(threading.Lock)的地方,一个都少不了。

5. C 扩展的 wheel 覆盖不均
要真正自由线程,C 扩展得发布 t ABI 的 wheel(cp313t / cp314t)。目前 PyPI 上覆盖并不均匀,依赖某个还没出 t wheel 的包,就会触发第 1 条的静默回退。

6. 不是所有代码自动变快
重申:I/O 密集、或本身会释放 GIL 的 C 扩展(如 NumPy),上自由线程几乎无收益。

一张决策清单

把上面揉成一张"要不要上自由线程"的速查表:

• 你的瓶颈是 纯 Python 的 CPU 计算吗?

• 否(I/O 密集 / 大量 NumPy 等 C 扩展)→ 不用,GIL 本来就不挡你。

• 任务能并行拆分、且少共享可变状态吗?

• 否(强依赖共享状态 / 竞态难处理)→ 先改架构,或退回多进程。

• 你的依赖里,关键 C 扩展都有 cp314t wheel 吗?

• 否 → 大概率静默回退,先用 -W default::RuntimeWarning 验证。

• 你能接受单线程慢 5–10%、内存多 15–20% 的代价吗?

• 否(单线程为主 / 内存吃紧)→ 不用,收益覆盖不了代价。

• 以上全过 → 上 python3.14t + ThreadPoolExecutor,起飞。

一句话决策:多核 + 纯 Python 计算 + 依赖齐全 + 能接受小代价 = 香;反之,GIL 版本更稳更省。

结尾:去跑一遍

写了这么多,最实在的建议只有一个:把上面的基准代码复制到本地,在 python3.14 和 python3.14t 下各跑一遍。你机器上的加速比、单线程回退、内存涨幅,才是最有说服力的数据,也最能帮你判断值不值。

自由线程的 3.14,不像是"终于能甩掉 GIL"的终点,更像"可以开始认真用 GIL-free"的起点。它香不香,取决于你手上的活儿,配不配得上这把新钥匙。

更多相关技术内容咨询欢迎前往并持续关注好学星城论坛了解详情。

想高效系统的学习Python编程语言,推荐大家关注一个微信公众号:Python编程学习圈。每天分享行业资讯、技术干货供大家阅读,关注即可免费领取整套Python入门到进阶的学习资料以及教程,感兴趣的小伙伴赶紧行动起来吧。

attachments-2022-05-rLS4AIF8628ee5f3b7e12.jpg

 

你可能感兴趣的文章

相关问题

0 条评论

请先 登录 后评论
Pack
Pack

2545 篇文章

作家榜 »

  1. Pack 2545 文章
  2. 轩辕小不懂 2403 文章
  3. 小柒 2228 文章
  4. Nen 576 文章
  5. 王昭君 216 文章
  6. 文双 71 文章
  7. 小威 64 文章
  8. Cara 36 文章