Python 3.15 来了:free-threading 稳定 ABI 能给高并发服务带来什么

📅 2026/8/24 20:58:22
Python 3.15 来了:free-threading 稳定 ABI 能给高并发服务带来什么
摘要Python 3.15 free-threading 拿到稳定 ABIC 扩展一次编译、跨版本复用。但非默认构建、单线程 1%–8% 回退、C 扩展未适配会静默重启用 GIL。本文用公开基准算性能账给出要不要上决策树与sys._is_gil_enabled()自检.8 月 4 日Python 3.15 rc1 发布。朋友圈刷屏的是 lazy imports、frozendict 这些新特性但我盯着的是另一条不起眼的公告free-threading 构建拿到稳定 ABI 了。为什么盯着这条因为过去两年我把Python 后端 CPU 密集这个组合翻来覆去地权衡过。线程吃不满多核多进程内存翻几倍——这笔账写 Python 后端的人都算过。先把结论放在前面abi3t 稳定 ABIPEP 803是 free-threading 从能跑走向能上规模的关键一块基础设施但它不是免费的午餐。三个代价——非默认构建、单线程 1%–8% 损耗、C 扩展未适配会静默重启用 GILGlobal Interpreter Lock全局解释器锁——决定了它今天只适合一部分服务。下面把账算细。一、老问题GIL 拆了轮子却要反复造free-threading无 GIL 构建从 3.13 就有了实验版。但有和能用之间隔着一条工程鸿沟C 扩展。以前的情况是每出一个新的 free-threaded 小版本C 扩展就得重编一次 wheel发行包。NumPy、Pandas 这类重型依赖还好社区追着适配但长尾的自研扩展、内部依赖呢每次版本升级都是一轮重新编译、重新验证。对一个要稳定运行的服务来说这成本不可接受。PEP 803 的 abi3t 就是冲这个来的C 扩展按稳定 ABIApplication Binary Interface应用二进制接口编译一次产物可以在多个 free-threaded 小版本之间复用不用重编。打个类比——以前是每换一次发动机就得重造整辆车现在发动机接口标准化了换个车型还能接着用。代价也有想吃到 abi3t部分 C 扩展需要改写声明显式承诺free-thread-safe。这不是配置开关是一次性迁移成本。二、性能账本值不值先看数据讲适用性之前先把数据摆出来。下面这组是公开基准8 核 EPYCPython 3.13--disable-gil构建CPU 密集型任务方案耗时内存单线程42.8s120MB多进程6.8s840MBfree-threaded5.6s145MB三个观察第一free-threaded 比多进程还快 18%内存却只有多进程的不到五分之一。多进程那份 840MB是进程间数据复制的税。第二官方口径下 4 核 CPU 密集场景能拿到 3.1–3.5 倍提速。不是线性但足够香。第三也是最容易被忽略的单线程有 5%–10% 的回退。你的服务如果大部分时间单线程跑上 free-threading 是净亏。1%–8% 还是 5%–10%不同负载形态数字不同但方向一致——有代价。三、最阴的坑GIL 会静默回来这是我看来全文最重要的一节。一个 C 扩展如果没声明自己 free-thread-safe在 free-threaded 构建里导入时解释器会为整个进程静默重新启用 GIL——只给一条 warning不报错不停机。你的服务表面上跑在无 GIL构建上实际上 GIL 回来了多线程还是串行。验证方法很简单导入依赖之后立刻查importsysimportyour_c_extension# 换成你真实的 C 扩展依赖# 关键一步导入后确认 GIL 状态别信构建版本号print(GIL enabled:,sys._is_gil_enabled())# 输出 True 就说明有依赖把你拖回了 GIL 模式我把这套验证做成了一个固定的启动自检。CPU 密集基准的骨架长这样importsys,timefromconcurrent.futuresimportThreadPoolExecutordefcompute_heavy_hash(data:bytes)-int:acc0forbindata:acc(acc*31b)0xFFFFFFFFreturnaccprint(GIL enabled:,sys._is_gil_enabled())# 先确认 GIL 真关了if__name____main__:payloads[bytes(2_000_000)for_inrange(8)]starttime.perf_counter()withThreadPoolExecutor()aspool:resultslist(pool.map(compute_heavy_hash,payloads))print(felapsed:{time.perf_counter()-start:.2f}s)同一份代码GIL 开和关耗时差好几倍。谁跑谁知道。还有一个真实的竞态案例值得记一笔公开案例来源见文末环境说明某电商风控计数器从 GIL 构建迁到 free-threaded 后命中量偏低 5–10%。排查下来根因是counter 1不是原子操作——GIL 时代它碰巧安全现在真并发了丢更新就成了日常。修复要么显式加锁要么用threading.local()。GIL 拆掉的那一刻你十年的多线程直觉也跟着拆了一半。四、要不要上一棵决策树综合上面的账我把判断逻辑整理成了一棵决策树照着走就行翻译成文字版服务是不是 CPU 密集的并行热点不是——比如 IO 密集、异步已经够用——那 GIL 本来就不是你的瓶颈别动。所用的 C 扩展是否已适配 free-thread-safe没适配——要么等社区适配要么接受静默 GIL那就等于白迁。单线程 1%–8% 的回退能不能接受不能——比如延迟敏感的单线程路径那再等等。三关都过再上 no-gil 构建并且上线第一件事就是跑sys._is_gil_enabled()自检。据我的观察今天能三关全过的服务是那种纯 Python 计算逻辑 依赖都健康的类型——数据处理管道、批量计算服务这类。典型 Web 服务先让子弹飞一会儿。五、时间线与配套补一下版本节奏方便排期rc1 已于 8 月 4 日发布ABI 已锁定rc2 预计 9 月 1 日final 预计 10 月 1 日PEP 790。另外 3.15 自带了一个采样分析器 TachyonPEP 799stdlib 内置能 attach 到运行中的进程最高 1MHz 采样——判断该不该上之前先用它看看热点到底在哪。写在最后free-threading 稳定 ABI 解决的是规模化采用的最后一公里编译一次、跨版本复用。但 Python 生态真正补完这门课还要等长尾 C 扩展逐个适配。我的建议很简单现在就把sys._is_gil_enabled()自检和那套基准脚本放进你的工具箱新版本出来跑一遍用数据决定上不上而不是用热情。你的服务是 CPU 密集型吗C 扩展依赖清点过了没评论区聊聊你的判断。环境说明Python 3.15rc1 / 3.14t 对照基准数据来自公开测试8 核 EPYC竞态案例来自公开技术复盘已在文中标注决策树与配图为本仓库 resvg 本地渲染。标签Python3.15 / free-threading / GIL / 高并发 / 稳定ABI