编程 Python 3.14 无 GIL 实战:当 CPython 终于敢把全局锁摘下来——从延迟引用计数到偏向引用、从多线程裸跑到生产迁移的全链路拆解

2026-08-18 06:42:31 +0800 CST views 13

Python 3.14 无 GIL 实战:当 CPython 终于敢把全局锁摘下来——从延迟引用计数到偏向引用、从多线程裸跑到生产迁移的全链路拆解

一句扎心的话:Python 程序员背了三十年的「多线程是假的」这个锅,从 3.14 开始,终于可以部分甩回给解释器了。但这把锁摘下来之后,世界并不是自动并行、性能直接起飞的——它更像把一把粗大的全局大锁,换成了一屋子细碎的、需要你自己看懂的小锁。这篇文章带你把自由线程(free-threading,俗称「无 GIL」)CPython 的底层机制拆到底,配可运行代码,讲清楚它到底让什么变快了、又在哪里悄悄坑了你。


0. 引子:一个写了三十年的「谎言」

从 Python 2 时代起,每一本入门书、每一篇面经都会告诉你一句话:「Python 的多线程是假的,因为 GIL(全局解释器锁)的存在,多线程只能并发不能并行,多核 CPU 跑不起来。」

这句话在 CPython 里,对纯 Python 的 CPU 密集任务而言,三十年来基本是事实。你开 8 个线程做数值计算,任务管理器里 8 个核都在卖力地跳,但你的程序跑出来的时间,和单线程几乎一样——因为任意时刻,只有一个线程能真正握着那把 GIL 解释字节码。

于是 Python 社区发展出了一整套「绕过 GIL」的肌肉记忆:

  • CPU 密集?上 multiprocessing,用进程躲开 GIL(代价是内存翻倍、IPC 痛苦);
  • I/O 密集?用 asyncio 协程,在单线程里用事件循环切换(本质是并发不是并行);
  • 实在要真并行又想共享内存?C 扩展里 Py_BEGIN_ALLOW_THREADS 手动释放 GIL(NumPy、PyTorch 们就是这么干的)。

而到了 2025 年 10 月 Python 3.14 正式发布、再到 2026 年生态逐步成熟,CPython 第一次把「无 GIL 构建」从实验室玩具推进到了「可投产候选」。注意我用的词:可选、可投产候选,而不是「默认、银弹」。这篇文章就是要帮你搞清楚这两点之间的全部真相。


1. 背景:GIL 到底在保护什么,又为什么摘不下来

要理解自由线程难在哪,得先理解 GIL 当年为什么存在。

1.1 GIL 的真实职责

CPython 的内存管理核心是引用计数(reference counting)。每个对象头上挂着一个整型计数器 ob_refcnt:有人引用它就 +1,没人引用了就 -1,归零就释放内存。这套机制要求对 ob_refcnt 的所有修改都必须「原子且及时」。

在单线程时代这没问题。但一旦多线程同时访问同一个对象,两个线程同时 Py_INCREF 一个对象,如果不加保护,就可能出现「都读到旧值、都写回旧值+1,结果少计了一次」的经典竞态,进而导致对象被提前释放、悬空指针、解释器崩溃。

最简单的保护手段就是:任何线程想执行 Python 字节码、想碰对象,先去抢一把全局大锁 GIL。抢到了才能动,没抢到就等着。这就从根上保证了任意时刻只有一条线程在改引用计数,内存安全靠「串行化」换来。

1.2 为什么「直接把锁删了」会死

很多人第一反应是:那把 ob_refcnt 的加减换成原子操作(atomic<int>)不就行了?

理论上行,工程上是灾难:

  1. 原子操作也有代价。现代 CPU 的原子自增(lock xadd)会强制让相关缓存行在所有核之间「弹来弹去」(cache-line bouncing)。当几十条线程疯狂对热门对象(比如 None、常见字符串、循环变量)做 incref/decref 时,原子锁会变成比 GIL 还狠的全局瓶颈——所有核在抢同一根缓存线,比排队还慢。
  2. C 扩展全部假设 GIL 存在。三十年的 C 扩展(NumPy、lxml、cv2……)在写的时候从没考虑过「我和别的线程同时在改这块内存」,它们内部大量非线程安全的共享状态。摘掉 GIL,这些扩展会立刻崩。
  3. 不止引用计数。CPython 还有垃圾回收器(处理循环引用)、小对象分配器(arena/pool)、导入锁、以及各种全局字典,全都是「假设串行」写的。

所以「无 GIL」不是删一把锁,而是把整套内存与并发模型重写一遍。这正是 Python 3.13 首次放出实验性自由线程构建、3.14 大幅打磨的工程体量。

1.3 三次历史性冲锋

  • Gilectomy(2015–2017,Larry Hastings):给每个对象加锁,结果性能暴跌(就是上面说的缓存行弹跳问题),最终搁置。它证明了「简单加锁」走不通。
  • PEP 554 / PEP 684(子解释器各自的 GIL):与其去掉全局锁,不如让多个解释器各持一把锁、共享内存但不能互相干扰。这是自由线程的「兄弟方案」,后面会讲它和自由线程怎么共存。
  • PEP 703(可选项关闭 GIL,Sam Gross):这才是正主。它没有用「给每个对象加锁」的朴素思路,而是用了一套组合拳——延迟引用计数 + 偏向引用 + 永生对象 + 对象级锁 + Stop-The-World GC。下面四节,一节一个。

2. 核心机制一:延迟引用计数(Deferred Reference Counting)

这是自由线程 CPython 的基石,也是它和 Gilectomy 最根本的区别。

2.1 问题本质

在带 GIL 的构建里,几乎每一行 Python 代码背后都有无数次 Py_INCREF / Py_DECREF:函数调用压栈要 incref 参数,出栈要 decref,for 循环取迭代器要 incref……这些操作在 GIL 保护下是「免费」的,因为反正没人跟我们抢。

一旦去掉 GIL,把每一次 incref/decref 都改成原子操作,上百万次原子自增打在同一个缓存行上,性能直接雪崩。

2.2 解法:把「即时引用计数」变成「延迟引用计数」

自由线程构建做了一个大胆的决定:

绝大多数 Py_INCREF / Py_DECREF 在函数调用热点路径上,直接变成「空操作(no-op)」。对象的真正释放,交给一个专门的、周期性运行的垃圾回收器来统一处理。

换句话说,对象的「引用计数」不再追求实时精确,而是延迟到 GC 阶段统一结算。GC 会遍历所有存活对象,重新数一遍谁还活着,然后释放那些真的没人的。

这带来一个有趣的副作用,也是你排查问题时最容易踩的坑:

import sys

# 永生对象 + 延迟计数,让 getrefcount 在很多对象上「失真」
x = [1, 2, 3]
print(sys.getrefcount(x))      # 自由线程构建里,这个值可能已经不可信

在自由线程构建下,sys.getrefcount 的语义被弱化——它不再(也不能)精确反映每一处 incref/decref,因为热点路径上根本没记账。如果你写了依赖 getrefcount 做引用泄漏探测的工具,在自由线程构建上要重新评估。

2.3 哪些对象仍走「即时计数」

不是所有对象都延迟。CPython 对两类对象仍保留即时引用计数:

  1. 永生对象(见 2.4 节):引用计数被冻住,永不改变。
  2. 需要精确生命周期的少数场景:比如某些 C 扩展接口强依赖即时计数语义。

其余绝大多数普通对象,都进入「延迟结算」队列。GC 在自由线程构建里因此承担比 GIL 构建重要得多的职责——它不只是处理循环引用,而是几乎接管了所有内存回收。


2.4 核心机制二:永生对象(Immortal Objects)

光延迟还不够。有一类对象,全程序共享、被 incref/decref 得最频繁:NoneTrueFalse、小整数(small ints)、被 intern 的常见字符串(比如属性名 "append""__init__")、各种模块级单例。

如果连这些也走原子计数,缓存行照样弹蹦到死。于是自由线程构建引入永生对象

在解释器启动期创建、预期程序全程存活的对象,被标记为「永生」:它的 ob_refcnt 被设成一个特殊的哨兵值(接近 INT_MAX 的常量),从此任何 incref/decref直接跳过它,永远不增不减、永不释放。

你可以亲眼验证:

import sys

# 永生对象的引用计数被冻在一个巨大常量上
print(sys.getrefcount(None))   # 输出一个接近 INT_MAX 的天文数字
print(sys.getrefcount(True))
print(sys.getrefcount(0))      # 小整数也是永生的

这带来的好处是:热门全局单例的引用计数字段再也不会被任何线程触碰,彻底消灭了最热的缓存行竞争点。代价是:这些对象占的内存永远不回收(但反正它们本来就活到程序结束)。

实战提示:如果你在自由线程构建里看到某个常量对象「引用计数异常高」,别慌,那是永生哨兵,不是内存泄漏。


3. 核心机制三:偏向引用(Biased References)

延迟引用计数解决了「共享热门对象」的问题,但普通对象呢?一个函数里的局部变量 local = SomeObject(),在单线程语境下只会被当前线程碰。对这种**「大概率只被一个线程拥有」**的对象,如果每次都走原子操作,依然是浪费。

自由线程构建用了一个叫 偏向引用(biased references) 的巧妙设计:

对象的引用计数默认处于「偏向」状态——表示「这个对象目前只被一个线程访问」。偏向状态下的 incref/decref 走普通(非原子)的整数加减,和 GIL 构建一样快,因为没有竞争。只有当对象真的被多个线程同时持有时,CPython 才把它「去偏向(unbias)」,切换成原子操作模式。

可以把它类比成操作系统的「偏向锁(biased locking)」思想:先假设无竞争、用最便宜的路径,等真有竞争了再升级。区别在于这里不是为了「锁」,而是为了「引用计数的原子性开销」。

这套机制的关键是成本只在「跨线程共享发生时」才支付。绝大多数短命的局部对象从生到死都在一个线程里,全程零原子开销。这也解释了为什么自由线程构建的单线程性能损耗其实比直觉小得多——它并没有给每个对象无脑上原子锁,而是「乐观假设单线程、悲观兜底多线程」。


4. 核心机制四:对象级锁与 Stop-The-World

延迟计数 + 偏向引用 + 永生对象,解决了「内存怎么安全释放」的问题。但还有一类问题没解决:可变容器(dict / list / set)的并发读写

4.1 每个容器一把小锁

在 GIL 构建里,你能安全地从两个线程同时 dict[k]=v,是因为 GIL 保证这两条语句不会交错。去掉 GIL 后,CPython 给每个可变容器对象配了一把极轻量的锁(实现上是一个压缩在对象头里的 _PyMutex,只占用很少字节)。

效果:

  • 两个线程同时改同一个 dict → 串行执行,不会崩;
  • 两个线程分别改两个不同的 dict → 真正并行,各拿各的锁,互不阻塞。

这比 GIL 的「全宇宙一把锁」精细太多了。

4.2 但「容器安全」≠「逻辑安全」

这是自由线程最容易被误解的地方,也是生产事故高发区。看这段代码:

import threading

counter = {"n": 0}

def worker():
    for _ in range(200_000):
        counter["n"] += 1   # 看似简单

threads = [threading.Thread(target=worker) for _ in range(4)]
for t in threads: t.start()
for t in threads: t.join()

print(counter["n"])   # 你期待 800000,实际会明显偏小

为什么会丢更新?因为 counter["n"] += 1 在字节码层面是三步

  1. LOAD 取出 counter["n"] 的当前值(dict 读取,有锁,安全);
  2. 在 Python 层做 + 1(纯计算);
  3. STORE 把新值写回 counter["n"](dict 写入,有锁,安全)。

第 1 步和第 3 步各自线程安全,但第 1 步和第 3 步之间,另一个线程可能已经插进来做过一次完整的读改写。于是两个线程「都读到 5、都写回 6」,一次更新就丢了。

结论:自由线程消除了「解释器崩溃」级别的竞态,但没有消除「业务逻辑」级别的竞态。 任何「读-改-写」复合操作,只要涉及共享可变状态,你照样需要 threading.Lock。区别只是——现在这把 Lock 里面的临界区,是真的能在多核上并行于其他非临界区代码的,而不像 GIL 时代那样整个进程都被串行化。

正确写法:

import threading

counter = {"n": 0}
lock = threading.Lock()

def worker():
    for _ in range(200_000):
        with lock:
            counter["n"] += 1

threads = [threading.Thread(target=worker) for _ in range(4)]
for t in threads: t.start()
for t in threads: t.join()

print(counter["n"])   # 800000 ✓

4.3 Stop-The-World:GC 需要「静止的全景照」

延迟引用计数靠 GC 周期性结算。但 GC 要准确判断「谁还活着」,必须看到一份自洽的、不被并发修改打断的对象关系快照。可自由线程下对象图一直在变,GC 没法在「边跑边改」中理清引用关系。

于是自由线程 CPython 引入了 Stop-The-World(STW):GC 要开工时,通过埋在字节码解释循环里的「安全点(safepoint)」标志,让所有跑着 Python 的线程协作式地暂停在最近的安全点,GC 拿到静止的全景照后快速扫描,再放行所有线程。

这意味着:自由线程程序会出现短暂的全线程暂停(通常亚毫秒,但在高分配压力下可能升高)。对绝大多数批处理、Web 服务无感;但对「硬性低延迟」场景(高频交易、实时音视频),这点和 Java GC 的 STW 一样,是需要观测和压测的。


5. 架构全景:自由线程 CPython 的内存与执行模型

把上面四块拼起来,自由线程 CPython 的完整画像是这样的:

┌──────────────────────────────────────────────────────────┐
│                    一个 Python 进程                        │
│                                                           │
│  解释器 A(可选自带 GIL,PEP 684)   解释器 B(同上)       │
│        │                                        │         │
│        └──────────────┬─────────────────────────┘         │
│                       ▼                                    │
│   自由线程核心运行时(无全局大锁)                          │
│   ├─ 延迟引用计数:热点 incref/decref = no-op             │
│   ├─ 偏向引用:单线程对象走廉价非原子加减                  │
│   ├─ 永生对象:热门单例引用计数冻结,零竞争              │
│   ├─ 每容器 _PyMutex:不同容器真并行,同容器串行        │
│   └─ Stop-The-World GC:周期性静止快照结算存活对象      │
└──────────────────────────────────────────────────────────┘

注意两个常被混淆的「兄弟特性」:

  • PEP 684 子解释器各自 GIL:让一个进程里跑多个解释器,每个解释器自己一把 GIL、各自内存隔离。它解决的是「多租户隔离」,和「无 GIL」是不同维度。
  • 自由线程(PEP 703):单个解释器内部去掉 GIL,让线程真并行。

两者可以叠加:你可以在一个进程里开多个子解释器,而每个子解释器又是自由线程的。不过日常 99% 的场景,你只需要关心「我的解释器是不是自由线程构建」。


6. 代码实战一:检测你的 Python 是不是自由线程构建

动手前先确认手里的刀是不是对的。有三种探测方式:

import sys
import sysconfig

def is_freethreaded_build():
    """这个二进制是不是用 --disable-gil 构建出来的"""
    return sysconfig.get_config_var("Py_GIL_DISABLED") == 1

def is_gil_currently_off():
    """运行时 GIL 当前是否处于关闭状态(自由线程构建里可经 PYTHON_GIL 重新打开)"""
    if hasattr(sys, "_is_gil_enabled"):
        return not sys._is_gil_enabled()
    return False

print("构建类型(是否无 GIL 二进制):", is_freethreaded_build())
print("运行时 GIL 是否关闭:", is_gil_currently_off())
print("版本串:", sys.version)

判断逻辑:

  • Py_GIL_DISABLED == 1:这是「无 GIL 二进制」(可执行文件通常是 python3.14t 这种带 t 后缀的)。
  • 在自由线程二进制上,你还可以通过环境变量 PYTHON_GIL=1-X gil=1 重新打开 GIL,方便做「同一份代码、开/关 GIL」的 A/B 对照实验。这是排查「到底是 GIL 还是我的代码逻辑在拖慢」的神器。

7. 代码实战二:真并行 CPU 计算——感受多核

理论说完,上最能说明问题的 benchmark。一个纯 Python 的 CPU 密集函数,分别用单线程和 4 线程跑:

import time
import threading

def cpu_work(n: int) -> float:
    """纯 Python 数值累加,刻意避免任何 C 扩展释放 GIL"""
    s = 0.0
    for i in range(n):
        s += (i % 7) * 1.0 / (i + 1)
    return s

N = 4_000_000

# 单线程:顺序跑 4 份
t0 = time.perf_counter()
for _ in range(4):
    cpu_work(N)
single = time.perf_counter() - t0

# 4 线程:同一份工作量分摊
def run():
    cpu_work(N)

t0 = time.perf_counter()
threads = [threading.Thread(target=run) for _ in range(4)]
for th in threads: th.start()
for th in threads: th.join()
multi = time.perf_counter() - t0

print(f"single : {single:.3f}s")
print(f"4-thread: {multi:.3f}s")
print(f"加速比 : {single / multi:.2f}x")

预期结果

构建单线程4 线程加速比
带 GIL(普通 python3.14~T~T(甚至更慢)≈ 1.0x(线程被串行化)
自由线程(python3.14t~T'(T' 略大于 T,见 9 节)~T'/4 + 开销≈ 3.5x+

这一步是自由线程「值不值」的分水岭:如果你的瓶颈是纯 Python 的 CPU 计算,且数据彼此独立,自由线程能直接把多核利用起来,而以前你只能靠 multiprocessing 把内存翻几倍去换。


8. 代码实战三:共享可变状态的陷阱(再强调一次)

第 4.2 节讲的竞态,在自由线程下反而更值得警惕——因为 GIL 时代你「以为」没问题其实是「被串行化保平安」,自由线程把你这层虚假的保护撤掉了。再给一个更隐蔽的例子:

import threading

results = []
lock = threading.Lock()

def append_work(tag):
    # 反例:先判断再操作,中间有窗口
    if len(results) < 10:
        results.append(tag)          # 两个线程可能同时通过 len 检查

threads = [threading.Thread(target=append_work, args=(i,)) for i in range(20)]
for t in threads: t.start()
for t in threads: t.join()

print(len(results))   # 期望 ≤10,自由线程下很容易超过 10!

「检查长度 → 追加」这两步在 GIL 下因为串行化,竞态概率极低(但并非绝对安全);在自由线程下,20 个线程同时抢,越界是常态。正确做法是用锁把「检查+追加」包成一个原子区间,或者用 queue.Queue(其内部已做线程安全封装)。

黄金法则:自由线程改变了「会不会崩」,但没改变「并发逻辑要自己保证正确」。任何共享可变状态,先问自己「读-改-写是不是原子的」,不是就上锁。


9. 代码实战四:I/O 密集与 C 扩展——真正的赢家其实是它们

很多人以为自由线程是给「纯 Python 循环」准备的,其实更大的受益者是 I/O 密集和 C 扩展场景

9.1 阻塞 I/O 不再卡死整进程

GIL 时代,time.sleep、网络阻塞调用在释放 GIL 后能让别的线程跑,但「释放 GIL」这件事本身依赖 C 扩展配合。自由线程下,Python 层的阻塞调用不再需要抢 GIL 才能交还控制权,线程调度更顺。对于「一堆线程各自等网络/磁盘」的服务,吞吐量会更好。

9.2 C 扩展:NumPy 早就「无 GIL」了

关键认知:像 NumPy、PyTorch 这类计算库,它们的重型计算本来就在 C 层、并且用 Py_BEGIN_ALLOW_THREADS 释放了 GIL。所以在 GIL 构建下,开多线程做矩阵乘法也能真并行——只要你别在 Python 层干等。

自由线程带来的额外收益是:Python 层的编排逻辑也不再被 GIL 串行化。以前「Python 里循环调度多个 numpy 任务」会被 GIL 拖住;现在 Python 层调度本身也能并行。

import numpy as np
import threading

def matmul_worker(seed: int):
    rng = np.random.default_rng(seed)
    a = rng.random((2000, 2000))
    b = rng.random((2000, 2000))
    return a @ b          # numpy 内部释放 GIL,多线程可真并行

threads = [threading.Thread(target=matmul_worker, args=(i,)) for i in range(4)]
for t in threads: t.start()
for t in threads: t.join()

但有个硬前提:这些 C 扩展必须提供自由线程版本的轮子(wheel)。好消息是 NumPy 2.x、PyTorch 等主流库在 2025–2026 已陆续发布自由线程兼容构建。坏消息是:大量小众、年久失修的 C 扩展还没适配,在自由线程下直接 import 可能崩溃或行为异常。这也是生产迁移最大的拦路虎(见第 11 节清单)。


10. 性能真相:benchmark 与必须说清的数字

这一节专治「无 GIL 一定快」的幻觉。社区基准(2025–2026 多方测试)呈现的规律很一致:

10.1 单线程有可见开销

同样一份单线程纯 Python 代码,在自由线程构建上通常比 GIL 构建慢 10%–30%。来源是:延迟引用计数让 GC 更忙、永生对象与偏向引用引入簿记、容器操作要拿 _PyMutex 小锁。这部分开销是「为并行能力交的税」。

所以单线程脚本盲目切自由线程,只会更慢

10.2 多核 CPU 密集能线性起飞(条件苛刻)

当任务是「多份互相独立、纯 Python、CPU 密集」时,自由线程能接近线性加速(4 核 ≈ 3.5x–4x,扣掉锁和 GC 开销)。这正是第 7 节 benchmark 验证的。

10.3 共享状态是隐形天花板

一旦线程频繁竞争同一把容器锁或同一份数据,加速比会迅速回落——Amdahl 定律在这里赤裸裸生效。你能并行多少,取决于「真正无共享」的工作量占比。

10.4 一个可复用的压测模板

import time, threading, statistics

def bench(func, n_threads, repeats=3):
    samples = []
    for _ in range(repeats):
        t0 = time.perf_counter()
        threads = [threading.Thread(target=func) for _ in range(n_threads)]
        for th in threads: th.start()
        for th in threads: th.join()
        samples.append(time.perf_counter() - t0)
    return statistics.median(samples)

# 用法:分别用 1/2/4/8 线程跑,画出加速比曲线,确认是否线性
for nt in (1, 2, 4, 8):
    print(nt, "threads ->", round(bench(cpu_work_wrapped, nt), 3), "s")

负责任地声明:上面所有百分比都是社区多轮基准的「典型区间」,具体数字随硬件、任务、Python 小版本浮动。生产决策前,务必用你自己的真实负载跑一遍


11. 生产迁移清单(Checklist)

如果你看完上面的坑,仍决定把某个服务迁到自由线程,照着这份清单来:

1. 拿到正确的二进制

# 从源码构建自由线程版本
./configure --disable-gil --prefix=$HOME/py314t
make -j"$(nproc)"
make install
# 可执行文件带 t 后缀,避免和原 python 混淆
$HOME/py314t/bin/python3.14t -V

或用官方安装包(3.13+ 的安装器已带「自由线程」勾选项),以及部分发行版的 python3.14t 包。

2. 盘点并升级所有 C 扩展

  • pip list 导出依赖,逐一确认是否提供自由线程 wheel;
  • NumPy、SciPy、PyTorch、Pandas 等主流库已就绪;小众 C 扩展需自行验证或替换;
  • 任何依赖 PyThreadState_GETPyEval_* 等旧线程 API 的扩展,需要按新 C-API 重写。

3. 并发安全审计

  • 全局搜索「读-改-写」模式:counter += 1、先 ifappend、缓存 dict 的懒加载;
  • 给所有跨线程共享的可变状态加 threading.Lock(或换成 queue.Queue);
  • 把 GIL 当成「免费锁」的旧代码,现在都要显式加锁。

4. 压测与观测

  • 用第 10.4 节模板验证加速比是否线性;
  • 观测 GC 的 Stop-The-World 暂停(gc 模块统计 + 外部 APM);
  • 关注尾部延迟(p99/p999),确认 STW 没捅穿你的 SLA。

5. 什么时候不要用自由线程

  • 单线程脚本、CLI 工具:只有开销没有收益;
  • 重度依赖未适配 C 扩展的旧系统:先别动;
  • 硬性低延迟、对 STW 零容忍的场景:需充分验证;
  • 只是 I/O 密集且已用 asyncio 跑得很好的服务:收益有限,迁移风险不划算。

12. 总结与展望

回到开头的那句话。Python 3.14 的自由线程,不是「GIL 死了」,而是「GIL 从必选项变成了可选项」:

  • 默认构建仍带 GIL。3.14 没有把自由线程设为默认,生态还在追。你主动构建/选用 python3.14t 才进入无 GIL 世界。
  • 它真正放开的,是「纯 Python 的 CPU 并行」。这是过去三十年 multiprocessing 霸占的领域,现在有了共享内存、零 IPC 成本的替代。
  • 它没放开的,是「并发正确性要自己负责」。容器级锁保证了不崩溃,但业务级竞态原封不动地留给了你。
  • 底层是一场精密的工程权衡:延迟引用计数 + 偏向引用 + 永生对象 + 对象锁 + STW GC,用「乐观 + 兜底」的组合,绕开了 Gilectomy 当年被缓存行弹蹦干掉的死局。

站在 2026 年看,自由线程的意义不只是一把锁的移除,而是给 Python 这艘巨轮换了一套并发引擎:短期(3.14–3.15)它是勇敢者的生产实验;中长期,随着 C 扩展生态全面适配,自由线程二进制极有可能在未来某个版本成为默认——到那天,「Python 多线程是假的」这句背了三十年的谎言,才算真正翻篇。

而你现在要做的,不是急着把所有服务切过去,而是理解它、用 benchmark 量它、在真正吃多核红利的地方谨慎启用它。锁摘下来了,但怎么用好这把「拆成碎片的小锁」,才是自由线程时代程序员的真本事。


参考主线:PEP 703(可选项关闭 GIL)、PEP 684(每子解释器 GIL)、Python 3.13/3.14 自由线程构建与官方文档、社区多轮性能基准。文中代码均可在自由线程二进制(如 python3.14t)与 GIL 二进制(如 python3.14)上对比运行。

复制全文 生成海报 Python 3.14 无 GIL 自由线程 GIL CPython

推荐文章

Vue3中的响应式原理是什么?
2024-11-19 09:43:12 +0800 CST
2024年微信小程序开发价格概览
2024-11-19 06:40:52 +0800 CST
Vue3中的Store模式有哪些改进?
2024-11-18 11:47:53 +0800 CST
Vue3中如何处理组件间的动画?
2024-11-17 04:54:49 +0800 CST
程序员茄子在线接单