Chapter 48: Free-Threaded Python
Free-threaded Python 讨论的是 CPython 在移除全局解释器锁(Global Interpreter Lock, GIL)之后,如何继续维护对象生命周期、容器结构、线程状态和 C 扩展边界。读完本章,你应能追踪一段多线程 Python 代码在 free-threaded build 中经过哪些 runtime 对象,判断哪些状态由 CPython 内部同步保护,哪些状态仍需要业务代码显式同步。
本章的贯穿材料是一个共享统计器。它很短,却能暴露 free-threaded Python 的主问题:多个线程同时读取、修改同一个 Python 对象图时,解释器内部安全、对象生命周期安全和业务状态正确性分别由谁承担。
from collections import defaultdict
from threading import Lock, Thread
counts = defaultdict(int)
counts_lock = Lock()
def record(kind: str, n: int) -> None:
for _ in range(n):
with counts_lock:
counts[kind] += 1
threads = [
Thread(target=record, args=("hit", 100_000)),
Thread(target=record, args=("hit", 100_000)),
]
for worker in threads:
worker.start()
for worker in threads:
worker.join()
print(counts["hit"])
在默认 CPython build 中,GIL 让同一时刻通常只有一个线程执行 Python bytecode。这个事实保护了大量解释器内部共享结构,也让许多 C 扩展把 GIL 当作隐含保护。free-threaded build 改变了这个前提:多个线程可以同时执行 Python 代码,CPython 必须把原先集中在 GIL 上的保护拆到引用计数、容器锁、内存分配器、垃圾回收、线程状态和扩展模块声明上。官方演进以 PEP 703 – Making the Global Interpreter Lock Optional in CPython 为主线;Python 3.13 起提供 free-threaded build 支持,Python 文档的 Python support for free threading 记录了使用层面的识别方式、线程安全边界和已知限制。
本章的核心判断是:free-threaded Python 扩大了 CPU 并行执行空间,同时把并发正确性从一个全局锁拆成多层局部约束。读代码时要先分清 Python 语义、CPython 实现保护和业务级同步三层。counts_lock 属于业务级同步;dict、list、引用计数和 allocator 的内部保护属于 CPython 实现策略;C 扩展的 module state、global cache、borrowed reference 和直接字段访问属于扩展边界。
48.1 no-GIL architecture
no-GIL architecture 的起点是 build 模式。PEP 703 为 CPython 增加 --disable-gil 这一构建方向,free-threaded build 会暴露 Py_GIL_DISABLED 相关配置,并允许解释器在没有全局 GIL 的模式下运行。Python 层可以用 sysconfig.get_config_var("Py_GIL_DISABLED") 判断 build 是否支持 free threading,用 sys._is_gil_enabled() 判断当前进程中 GIL 是否处于启用状态。由于第三方 C API extension 可能尚未声明支持 free threading,free-threaded build 也支持在运行时重新启用 GIL;这属于兼容策略,说明 free-threaded Python 在 Python 3.13 / 3.14 阶段仍是渐进迁移模型。
import sys
import sysconfig
supports_free_threading = bool(sysconfig.get_config_var("Py_GIL_DISABLED"))
gil_enabled = sys._is_gil_enabled()
print(supports_free_threading, gil_enabled)
这段代码回答的是 runtime 配置问题。Py_GIL_DISABLED 描述当前解释器二进制是否具备 free-threaded 能力,sys._is_gil_enabled() 描述进程此刻是否仍在使用 GIL。二者要分开判断,因为兼容扩展可能触发运行时 GIL 启用,构建能力和当前执行模式并非同一个状态。
free-threaded architecture 需要替换 GIL 曾经承担的几类保护。第一类是对象生命周期保护,核心是 Py_INCREF / Py_DECREF 的并发安全。第二类是容器结构保护,核心是 list、dict、set 等可变对象在并发读写时的内部一致性。第三类是 runtime 全局状态保护,核心是解释器状态、线程状态、GC 状态、allocator 状态和 import / module state。第四类是扩展模块边界,核心是 C 扩展是否把全局变量、borrowed reference、直接字段访问和缓存当作 GIL 保护区。
下面这张图给出 free-threaded build 中一次对象访问的分层检查路径。图只覆盖 CPython runtime 内部与 C API 边界,不覆盖应用层业务锁的设计。
图中的关键关系是:GIL 消失后,PyThreadState 仍然存在,线程仍然需要进入可访问 Python 对象的状态;变化发生在“允许同时进入”的数量上。PEP 703 设计中,多个线程可以同时处于 attached 状态,因此对象头、容器实现和 allocator 必须在更小粒度上处理竞争。对贯穿材料中的 counts 来说,CPython 会保护 dict 自身内部结构,使解释器进程维持安全;counts[kind] += 1 作为读取旧值、计算新值、写回新值的复合操作,仍由 counts_lock 表达业务级原子性。
48.2 lock strategy
free-threaded CPython 的 lock strategy 是把一个全局互斥点拆成对象级、容器级、线程状态级和扩展级同步。这个拆分的目标是让无关对象的访问可以并行,让真正共享的对象在局部范围内串行化。贯穿材料中的两个线程都访问 counts,因此共享点集中在同一个 defaultdict 对象和同一个键对应的值更新上。
内置容器的同步属于解释器实现层。Python 3.14 文档说明,free-threaded build 中 dict、list、set 等 built-in types 使用内部锁保护并发修改,使其行为尽量接近默认 GIL build 的当前行为。同时,文档也把这类保护描述为 CPython 当前实现行为,应用代码仍应使用 threading.Lock、queue.Queue、concurrent.futures 或更高层同步原语表达业务约束。这个边界直接对应 counts_lock:内部锁保护 dict 结构,外部锁保护“计数累加”这个业务动作。
锁粒度需要配合操作语义理解。len(shared_list) 这类只读取容器长度字段的操作可以用原子读取或轻量路径完成;list.append()、dict.__setitem__() 这类修改容器结构的操作需要获取对应容器的内部锁;list.extend(other_list)、dict.__eq__(other_dict) 这类同时访问两个容器内部结构的操作需要同时处理两个容器的锁。PEP 703 中还描述了部分 list / dict 读取路径的 optimistic locking 设计,它依赖 allocator 对内存页复用施加短期限制,从而减少常见读路径上的锁开销。
C API 扩展中的锁策略更容易出错,因为许多历史代码直接读取结构体字段或使用宏。Python 3.14 的 C API Extension Support for Free Threading 明确指出,PyList_GET_ITEM、PySequence_Fast_GET_SIZE 这类宏不会执行错误检查和锁保护;如果底层容器可能被并发修改,扩展应使用带锁语义或返回强引用的新 API。PyDict_Next() 也是典型例外,它遍历字典时不自动锁住整个字典,扩展代码应在可能并发修改的场景下使用 critical section 包住遍历。
Py_BEGIN_CRITICAL_SECTION(dict);
PyObject *key;
PyObject *value;
Py_ssize_t pos = 0;
while (PyDict_Next(dict, &pos, &key, &value)) {
/* read key and value while dict is protected */
}
Py_END_CRITICAL_SECTION();
这段 C 代码表达的是扩展边界的保护责任。Py_BEGIN_CRITICAL_SECTION 和 Py_END_CRITICAL_SECTION 是 free-threaded build 中为 Python 对象访问提供的 critical section API;它们在常规 build 中可以作为兼容路径存在。工程判断顺序是:先判断共享对象是否可能被并发修改,再判断 API 是否自动获取内部锁,再判断返回的是 borrowed reference 还是 strong reference,最后为扩展自己的 global cache 或内部字段增加独立同步。
48.3 runtime tradeoff
free-threaded 模式的收益来自真正的多核 Python 执行空间。对贯穿材料来说,如果真实任务是 CPU 上的大量 Python 级解析、调度、对象构造或轻量计算,两个线程在 free-threaded build 中有机会同时推进。对数据科学、AI 推理服务和高吞吐 I/O 协调来说,收益常出现在“Python 负责调度大量 native kernel、网络请求或数据流水线”的位置:过去多个线程会在重新进入 Python 代码时竞争 GIL,现在它们可以在多个 CPU core 上同时运行 Python 层调度逻辑。
成本同样来自这个设计。引用计数更新需要 atomic、biased 或 deferred 策略;容器修改需要内部锁;GC 需要暂停线程获得稳定对象图;allocator 需要配合乐观读路径控制内存复用;C 扩展需要声明和验证 free-threading 支持。Python 3.14 文档还给出一个使用层面的提醒:free-threaded build 的单线程执行会有额外开销,pyperformance 平均开销在不同平台上有差异。这个数据适合作为版本边界下的观察依据,不能直接推导某个业务服务一定变快或变慢。
贯穿材料中的 counts_lock 也展示了 tradeoff 的第一层:业务锁可以保证计数正确,同时会串行化热点更新。free-threaded build 让两个线程可以同时执行 Python 代码,但它无法让同一个共享键的复合更新天然并行。若所有线程都集中修改同一个 counts["hit"],热点会从 GIL 转移到 counts_lock、字典内部锁、引用计数和缓存行竞争上。若任务能按线程分片统计,最后合并结果,free-threaded build 更容易释放多核收益。
from collections import Counter
from threading import Thread
partials: list[Counter[str]] = []
def record_local(kind: str, n: int) -> None:
local = Counter()
for _ in range(n):
local[kind] += 1
partials.append(local)
threads = [
Thread(target=record_local, args=("hit", 100_000)),
Thread(target=record_local, args=("hit", 100_000)),
]
这段代码的设计意图是减少共享写入。每个线程优先更新本地 Counter,共享列表 partials 只在提交结果时被修改。它说明 free-threaded Python 的性能判断应看对象共享模式:共享热点越少,内部锁和 atomic refcount 竞争越少;对象在多个线程之间频繁传递,收益就会被同步成本抵消。换句话说,迁移 free-threaded build 的第一步是画出对象共享图,而非只统计线程数量。
调试复杂度也会提高。默认 GIL build 中曾经稳定复现的 interleaving,在 free-threaded build 中可能出现更多执行顺序。业务代码中对 dict 当前大小、两个字段之间关系、iterator 当前位置、frame object 状态的隐含假设,会在真正并行下暴露。Python 文档明确提示,正在其他线程执行的 frame 的 frame.f_locals 访问存在安全风险;同一个 iterator 被多个线程并发访问时,也可能出现重复或遗漏元素。工程上应把这些对象视为线程拥有的状态,通过 queue、lock、event 或复制传递跨线程数据。
48.4 memory synchronization
memory synchronization 解决的是“一个线程写入对象状态后,另一个线程按什么顺序、在什么边界看到这些状态”的问题。GIL 曾经在 CPython 中提供了强烈的串行化效果,许多代码把它误当作通用 memory model。free-threaded build 中,状态可见性需要由 atomic 操作、锁、critical section、线程同步原语和 C/C++ memory ordering 共同定义。
贯穿材料中的 counts_lock 建立了最小同步边界:进入 with counts_lock 后,读取旧值、计算新值、写回字典这三个动作形成一个受保护区间。另一个线程进入同一把锁时,会在锁释放之后观察到前一个线程写入后的状态。这里的保证来自 threading.Lock 的同步语义,而非 dict 内部锁。dict 内部锁保护容器结构;业务锁保护复合状态变更。
一个更容易出错的例子是两个字段表达一个状态机。下面的代码试图用 ready 表示 payload 已经准备好:
shared = {"ready": False, "payload": None}
def publish(payload: bytes) -> None:
shared["payload"] = payload
shared["ready"] = True
def consume() -> bytes | None:
if shared["ready"]:
return shared["payload"]
return None
在 free-threaded build 中,publish 的两个写入和 consume 的两个读取需要一个共同同步边界。内置 dict 的内部锁可以保护单次读写时字典结构一致,却不把“payload 已写入”和“ready 已置位”组合成事务。稳定写法是让两个字段在同一把业务锁内读写,或改用 queue.Queue、threading.Event 加锁保护的对象、不可变消息对象等更明确的同步结构。
from threading import Event, Lock
ready = Event()
shared_lock = Lock()
payload_box: dict[str, bytes | None] = {"payload": None}
def publish(payload: bytes) -> None:
with shared_lock:
payload_box["payload"] = payload
ready.set()
def consume() -> bytes:
ready.wait()
with shared_lock:
result = payload_box["payload"]
assert result is not None
return result
这段代码把可见性拆成两层:Event 表示跨线程通知,shared_lock 表示 payload 对象的读取和写入边界。对 Python 代码来说,优先使用标准库同步原语能把 memory synchronization 变成可审查结构。对 C 扩展来说,情况更细:atomic refcount 可以保证对象生命周期字段并发更新安全,不能自动保护扩展自有缓存;critical section 可以保护 Python 对象字段访问,但在阻塞或嵌套获取锁时可能暂时释放外层锁,因此恢复执行后要重新检查依赖的对象状态。
判断 memory synchronization 的顺序是:先定位共享状态,确认它是单个对象字段、多个对象之间的不变量,还是扩展模块内部全局状态;再确认访问路径是否经过 Python 标准同步原语、CPython 容器内部锁、C API critical section 或原子操作;最后检查读取方是否依赖多个值之间的顺序关系。只要读取方依赖“先写 A 再写 B”的关系,就需要一个能覆盖 A 和 B 的共同同步边界。
48.5 atomic reference counting
atomic reference counting 是 free-threaded CPython 最核心的对象生命周期问题。CPython 对象通常通过引用计数管理生命周期:创建新引用时增加计数,释放引用时减少计数,计数归零时触发释放。默认 GIL build 中,许多引用计数更新发生在持有 GIL 的区间内,因此普通非原子整数更新就能维持解释器内部安全。free-threaded build 允许多个线程同时持有和释放同一个对象引用,引用计数字段就变成真正的共享内存。
最直接方案是把所有引用计数更新改成 atomic increment / decrement。这个方案容易理解,却会让常见对象访问路径承担大量原子读改写成本,并引入 CPU cache line 竞争。PEP 703 因此采用组合策略:biased reference counting、deferred reference counting、immortal objects 和 stop-the-world GC 配合使用。它们共同目标是减少多线程热点对象上的 atomic 写入次数,同时让对象释放仍然有安全判断点。
biased reference counting 的工作模型是给对象记录一个 owning thread。对象由拥有线程访问时,引用计数走本地快速路径;其他线程访问时,走共享计数和 atomic 路径。对象的共享计数字段还带有状态位,例如 default、weakrefs、queued、merged,用来描述是否需要合并本地计数和共享计数,以及是否可以走快速释放路径。对贯穿材料来说,counts、字符串键、整数对象和线程栈上的临时引用都会经历大量引用变化;free-threaded build 要让这些变化在多个线程间保持生命周期安全。
这张状态图表达的是对象释放前的安全门槛。default 状态适合由 owning thread 快速处理的常见对象;当对象进入 weakrefs、queued 或 merged 路径后,释放前需要更多协调。读源码时不要把 ob_refcnt 再理解成一个简单字段;在 free-threaded build 的模型中,真实生命周期可能分散在本地计数、共享计数、线程栈跳过的 deferred 引用和 GC 计算结果中。
deferred reference counting 处理另一类热点对象,例如 top-level functions、code objects、modules 和 methods。这些对象经常被多个线程访问,同时又未必适合永久 immortal。deferred 策略允许解释器跳过部分栈 push / pop 引起的引用计数更新,把真实引用数的计算推迟到 GC 暂停点。结果是对象释放时间可能比默认 GIL build 更晚,__del__、weakref callback、资源释放和内存峰值判断都需要接受更宽的时间窗口。
immortal objects 则把某些长期存活、频繁共享的对象标记成引用计数不再变化的对象。PEP 683 – Immortal Objects, Using a Fixed Refcount 把 immortal objects 定义为 CPython 内部特性,重点是让运行时跳过这些对象的 refcount 修改,降低共享对象上的 cache invalidation。PEP 703 也利用了类似思想来减少 free-threaded 场景中的引用计数竞争。版本边界需要明确:哪些对象在某个 Python 版本中被 immortalize 是 CPython 私有实现细节,正文中的稳定结论是“热点共享对象的 refcount 写入会被压缩或跳过”,不是某个固定对象集合永远不变。
48.6 runtime redesign
runtime redesign 的范围超过引用计数。free-threaded Python 要让解释器、GC、allocator、C API 和 extension state 都接受“多个线程同时访问 Python 对象”这个前提。读 CPython 变更时,可以把 redesign 分成五个层级:thread state、object lifecycle、container operation、memory allocator、extension contract。
thread state 层级的核心是 PyThreadState 仍然存在。PEP 703 描述了 ATTACHED、DETACHED、GC 等线程状态:线程访问或修改 Python 对象前需要 attached;阻塞或释放执行权时可以 detached;GC 暂停期间线程进入 GC 状态。与 GIL build 的差异是,free-threaded build 允许多个线程同时 attached。这个变化把“能否访问 Python 对象”的检查和“是否独占解释器”的检查拆开了。
GC 层级需要获得稳定对象图。默认 CPython 已经有循环 GC,但 free-threaded build 下多个线程可能同时改变引用关系,因此 cycle detection 前需要 stop-the-world 暂停。PEP 703 还提出 free-threaded build 中使用非分代 GC,以降低频繁 young generation collection 对多线程程序的暂停影响。这里的工程结论是:free-threaded Python 提升了 Python 代码并行空间,同时把 GC 暂停变成更显性的 runtime 成本。
allocator 层级也要重新定义。PEP 703 选择以 mimalloc 替代传统 pymalloc 的一部分职责,并配合 list / dict optimistic access 控制内存页复用。原因在于,乐观读取容器元素时,另一个线程可能释放元素对象;allocator 需要保证在读取路径完成前,关键 refcount 位置仍可被安全检查。这个设计说明 free-threaded Python 的安全边界已经下沉到内存布局和 page reuse 规则,而非只靠容器对象上的一把锁。
C API 和 extension state 是迁移成本最高的位置之一。扩展模块需要明确声明支持 free-threaded 运行;多阶段初始化模块可以使用 Py_mod_gil slot,单阶段初始化模块可以在 free-threaded build 下调用 PyUnstable_Module_SetGIL()。未声明支持的扩展在导入时可能让运行时重新启用 GIL。下面的简化片段只展示声明位置,真实扩展还要审查 borrowed reference、直接字段访问、全局缓存和自有锁。
static struct PyModuleDef_Slot module_slots[] = {
#if PY_VERSION_HEX >= 0x030D0000
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
#endif
{0, NULL}
};
扩展迁移的检查顺序可以固定为五步。先检查 build 和运行时 GIL 状态,再检查模块是否声明 free-threading 支持;随后扫描直接结构体字段访问和宏访问;再替换可能被并发修改容器上的 borrowed reference API;最后把扩展内部 global state、cache、singleton registry、memory pool 和跨线程回调放入明确的锁或 thread-local storage。这个顺序能把“解释器保护”和“扩展自有保护”分开,减少把 C 层数据竞争误判成 Python 容器问题。
对纯 Python 代码,runtime redesign 的判断顺序也相似:先确认解释器是否 free-threaded,再画出共享对象图;再把共享对象分为内置容器、用户对象、迭代器、frame / traceback、外部资源句柄;最后为跨对象不变量加业务同步。贯穿材料中的 counts 属于内置容器加业务不变量;counts_lock 覆盖更新不变量;线程本地 Counter 方案通过减少共享对象数量来降低 runtime 同步成本。
48.7 CPython future roadmap
CPython 的并行路线可以用三条 PEP 串起来:PEP 684 – A Per-Interpreter GIL 把多解释器隔离和 per-interpreter GIL 推向可用形态,PEP 683 用 immortal objects 减少共享全局对象的引用计数修改,PEP 703 在可选 build 中让一个解释器内部的多个线程并行执行 Python 代码。三者解决的是同一个长期问题的不同层级:解释器隔离、共享对象生命周期、单解释器多线程执行。
PEP 684 的价值在于把 runtime state 拆到解释器级别。每个 interpreter 有自己的模块表、builtins、import state、thread list 和部分 runtime config;跨 interpreter 数据需要通过可安全传递的对象、序列化、复制或 channel。这个方向适合任务之间天然隔离、数据交换边界清晰的架构。它提升并行能力的方式是“多个解释器各自持有自己的 GIL”。
PEP 683 的价值在于降低全局共享对象上的 refcount 写入。None、布尔对象、部分 interned strings、静态类型对象等长期存活对象在多线程或多解释器场景中会被频繁访问。把适合的对象做成 immortal,可以减少 cache line invalidation,也能简化解释器之间共享某些不可变全局对象时的生命周期问题。它是 per-interpreter GIL 和 free-threaded Python 的底层铺垫之一。
PEP 703 的价值在于让单个解释器内的 Python 线程获得真正的 CPU 并行空间。它承担的改造最广:引用计数、GC、allocator、container thread-safety、C API 扩展声明和 runtime 配置都要参与。它的 rollout 被官方设计成渐进模式,原因也在这里:Python 生态中大量扩展模块、工具、框架和调试方式都曾把 GIL 当作环境前提,free-threaded build 需要生态逐步标注、测试和修复。
读未来 CPython 并发变化时,可以使用一个稳定判断框架。第一步,看变化发生在 process-wide runtime、per-interpreter state、per-thread state、object header、container implementation 还是 C API contract。第二步,看它影响的是语义可见行为、CPython 私有实现、性能路径还是扩展兼容性。第三步,看它增加的是并行能力、隔离能力、生命周期安全还是调试限制。第四步,把结论放回代码:共享对象是否减少,业务同步是否明确,扩展是否声明支持,benchmark 是否覆盖目标 workload。
回到本章贯穿材料,free-threaded Python 并不让 counts[kind] += 1 自动获得业务级原子性;它让两个线程更可能同时推进 Python 层执行,并由 CPython 在对象头、容器、allocator 和 GC 层维持解释器内部安全。稳定的工程做法是:用 free-threaded build 争取并行空间,用对象分片减少共享热点,用标准同步原语表达业务不变量,用 C API 审查扩展边界。
最小自检任务
阅读下面代码,并回答三个问题:在 free-threaded CPython 中,哪些安全性由解释器内部承担,哪些正确性由业务锁承担,哪些风险来自对象共享模式?
from collections import defaultdict
from threading import Lock, Thread
metrics = defaultdict(int)
metrics_lock = Lock()
def add(name: str, amount: int) -> None:
for _ in range(amount):
with metrics_lock:
old = metrics[name]
metrics[name] = old + 1
workers = [
Thread(target=add, args=("ok", 50_000)),
Thread(target=add, args=("ok", 50_000)),
]
for worker in workers:
worker.start()
for worker in workers:
worker.join()
print(metrics["ok"])
答案要点
解释器内部承担的是 defaultdict / dict 结构安全、对象引用生命周期安全、线程状态进入和退出安全,以及 allocator / GC 层面的对象图安全。free-threaded build 会用容器内部锁、引用计数策略、allocator 规则和 GC 暂停来维持 CPython 内部一致性。
业务锁承担的是 old = metrics[name] 到 metrics[name] = old + 1 之间的复合更新正确性。这个更新跨越读取旧值、创建或取得整数对象、计算新整数、写回字典多个动作;metrics_lock 把这些动作放进同一个同步边界,使两个线程不会基于同一个旧值写回。
对象共享模式带来的风险是热点集中。两个线程反复修改同一个 metrics 对象和同一个键,free-threaded build 可以让线程并行进入 Python 执行,但热点会集中到业务锁、容器内部锁、引用计数和 cache line 竞争上。若业务允许分片统计,线程本地 Counter 加最终合并通常能降低共享写入压力。
迁移判断顺序是:先确认 build 是否支持 free threading 和当前 GIL 状态;再画出共享对象图;再区分单次容器操作、复合业务不变量和跨对象状态机;然后用 Lock、Queue、Event 或线程本地对象表达业务同步;最后检查 C 扩展、iterator、frame object 和 borrowed reference 这类边界对象。
本章知识点总结
- no-GIL 架构:free-threaded CPython 把 GIL 曾经承担的保护拆到引用计数、容器锁、allocator、GC、线程状态和扩展模块声明上。
- 构建状态:
Py_GIL_DISABLED描述解释器是否具备 free-threaded 能力,sys._is_gil_enabled()描述当前进程中 GIL 是否启用。 - 容器内部锁:
dict、list、set的内部锁保护 CPython 容器结构一致性,业务级复合操作仍需要用户同步原语表达。 - 业务不变量:跨越多次读取和写入的状态关系需要同一把业务锁、queue、event 或不可变消息来建立同步边界。
- 锁粒度取舍:free-threaded build 增加多核执行空间,同时引入内部锁、atomic 操作、allocator 协调、GC 暂停和调试复杂度。
- 内存可见性:线程之间的对象状态可见性依赖锁、atomic、critical section 和标准库同步原语,多个字段之间的顺序关系需要共同边界覆盖。
- 引用计数改造:free-threaded CPython 使用 biased、deferred、immortal 和 GC 配合,减少热点对象上的 atomic refcount 成本。
- 对象生命周期:deferred reference counting 和 stop-the-world GC 会让部分对象释放时间变宽,资源释放和内存峰值判断要纳入这个版本边界。
- 线程状态:free-threaded build 中多个线程可以同时处于 attached 状态,访问 Python 对象前仍需要有效的
PyThreadState。 - allocator 约束:mimalloc 与页复用规则服务于乐观容器读取和对象生命周期安全,内存分配器成为并发安全设计的一部分。
- 扩展边界:C 扩展需要声明 free-threading 支持,并审查 borrowed reference、直接字段访问、宏访问、global cache 和内部状态锁。
- 演进主线:PEP 684 处理多解释器隔离,PEP 683 处理 immortal objects,PEP 703 处理单解释器内 free-threaded 执行。