count_words 的运行结果是 {"one":2,"two":1}。dis 又显示 FOR_ITER、BINARY_OP 和调用相关指令。那么 BINARY_OP 是 Python 语言规范承诺的实现方式吗?不是:词频结果与对象、迭代协议属于可向语言规则求证的行为;字节码指令、解释器执行循环和 GIL 都是本次冻结的 CPython 构建需要另外核实的实现细节。00 的冻结记录给出 /usr/bin/python3.12、CPython 3.12.3、Ubuntu 补丁包版本 3.12.3-1ubuntu0.17,因此不能只查滚动的主分支源码。

本次重新按 00 的 .dsc 和两份归档 SHA-256 核对源包,使用 dpkg-source -x 解出 Ubuntu 补丁后的源码,并逐一比较 Python/ceval.c、Python/generated_cases.c.h、Python/ceval_gil.c、Include/internal/pycore_gil.h 与原始 v3.12.3 tar 的 SHA-256;这四个文件在本版本比较结果相同。这个结论只覆盖列出的四个文件,不把全部发行版源码等同于上游 commit f6650f9ad73359051f3e558c2431a109bc016664。

实际走到的执行位置

下文 Ubuntu 源码解包、手工构建及缺 _sqlite3 的结果属于留存历史。2026-10-12 从固定上游 commit 下载四个参考文件,逐个核对原哈希与符号,并在 macOS CPython 3.12.3 的已安装 wheel 环境执行 38_implementation.py,退出码 0;命令和来源见 evidence/completion/additional/38-source.* 与 source-provenance.json。这证明参考文件匹配,不证明 uv 管理的二进制恰好由未经补丁的该 commit 构建。当前另一个 macOS 3.13.9 free-threaded 构建含 SQLite 3.50.4,已独立安装同一 wheel 并跑完整测试;其结果在 evidence/completion/acceptance/freethreaded-tests.stderr,不能反向把历史缺扩展构建改判通过。扩展导入前后的新 GIL 对照见 E03 的 evidence/completion/native/。

dis.get_instructions(count_words) 在当前进程列出 FOR_ITER、BINARY_OP 等指令;实验先断言字节码,再用原业务输入断言词频结果。冻结源码的 Python/ceval.c:654 定义 _PyEval_EvalFrameDefault,在 Python/ceval.c:847 引入 generated_cases.c.h;后者的 TARGET(FOR_ITER)、TARGET(CALL) 和 TARGET(BINARY_OP) 分别在生成文件第 3260、3700、4641 行。生成文件头部注明它从 Python/bytecodes.c 生成。阅读时区分“源码模板”、“构建出的指令分支”和“这次被 dis 观察到的函数指令”;不能从一个符号名推出所有 Python 实现都以这个分支运行,指令也可能在运行中自适应变化。

冻结源码的 Python/ceval_gil.c 中 take_gil 位于第 337 行,drop_gil 在第 273 行;开头说明 GIL 的锁与条件变量及 eval_breaker 的调度请求。这解释普通 CPython 3.12 下同一时刻的解释器执行约束,不能推出 submit/query/cancel 的多步骤业务状态原子性。28 的 Barrier 反例已经在同样普通构建中复现重复领取;这些代码还不能独立解释 36 的端到端样本为何快慢,池启动、序列化和负载形状都在计时边界内。

普通构建探针得到 sysconfig.get_config_var("Py_GIL_DISABLED") is None,sys._is_gil_enabled 在 3.12 不存在,主线程和受控新线程调用 PyGILState_Check 都返回 1。最后一个值只是探针当时调用线程持有 GIL,不代替支持运行时开关的解释器上的状态核对。为补这个缺口,另从 Python.org 的 3.13.9 归档在仓库外执行 ./configure --disable-gil && make -j4,冻结源码 tag 的完整 commit 8183fa5e3f78ca6ab862de7fb8b14f3d929421e0、归档 SHA-256 ed5ef34cda36cfa2f3a340f07cac7e7814f91c7f3c411f6d3562323a866c5c66、新二进制 SHA-256 7b2529b42b4ce47282b0681d12ca5f8b9c9315da29a9d8ada5f9f35cb82208dc。

这个 CPython 3.13.9 free-threaded 构建的 Py_GIL_DISABLED=1;独立进程里 sys._is_gil_enabled() 起初为 False,导入该构建的 _json 扩展后仍为 False。另一进程先导入任务模块并跑完前章同一词频/CPU 负载,前后均为 False。再单独编译一个没有 free-threaded 兼容声明的最小 legacyprobe C 扩展:它能加载,却明确告警并把运行时 GIL 改为 True;用 PYTHON_GIL=1 启动也得到 True。这四项分别说明构建模式、运行时开关、兼容扩展与不兼容扩展,不能互相替代。

free-threaded 同负载四种策略各采两次,结果全部正确且失败数为零;它与普通构建的版本、编译选项、可用扩展均不同,不能以两次壁钟时间计算“关闭 GIL 的加速倍数”。这个手工构建缺 _sqlite3、zlib 和 _ctypes(开发头文件不可用)。实际尝试全套测试,执行 88 项后因 _sqlite3 不可导入出现 4 个导入错误及 1 个由此引发的失败;不能把它报成累计回归通过。针对 asyncio 和线程的测试分别通过 8、2 项;进程测试第一次因错误文本从“pickl…”变成 “Can’t get local object”而误失败,修正测试对具体消息的过拟合后 2 项通过,普通 3.12 的 96 项回归仍通过,首轮日志保留。

1
2
3
4
5
cd examples/python-language
PYTHONPATH=src /usr/bin/python3.12 experiments/38_implementation.py --source /tmp/tasklab38-src
PYTHONPATH=src /usr/bin/python3.12 benchmarks/36_measure.py --samples 2 --iterations 20000
PYTHONPATH=src /usr/bin/python3.12 -m unittest discover -s tests -v
# free-threaded 的编译命令、运行时开关与扩展探针见本章 commands.txt

/tmp/tasklab38-src 是本次从 00 的已校验归档执行 dpkg-source -x 得到的仓库外目录;复跑时须先解出相同版本,不应在缺源码时默默退化成只查网页。源码归档哈希、四文件哈希与符号、字节码、普通与 free-threaded 构建各自的同负载样本、扩展告警、命令原始输出及普通构建累计 96 项测试在 examples/python-language/evidence/38/20261008-source/。样本不足以复述普遍性能趋势;3.13t 上缺失的 SQLite 集成不算通过。

预测题

dis 显示 BINARY_OP,源码存在 TARGET(BINARY_OP),且结果与顺序测试相同。由此能推出 PyPy 也走同一 C 标签,或 free-threaded 构建已经通过验证吗?

答案:单凭该字节码两者都不能推出。第一项是当前 CPython 的实现线索;第二项需要独立的自由线程构建、实际 GIL 开关和扩展兼容检查,本章另用 3.13.9t 对选定负载与两个扩展作了上述验证,尚未证明 SQLite 和所有依赖在 3.13t 可用。

变更题

把主线程 PyGILState_Check()==1 写成“GIL 保证领取一次且整个服务线程安全”,哪个观测可以推翻它?如何使新构建的主张可验?

答案:28 的屏障固定两线程交错时会重复领取;应使用锁或 SQLite 事务保护完整状态迁移。本次 3.13.9t 已分别冻结二进制和源码、验证构建标志与实际 GIL 开关,在同一负载及扩展导入前后复测;但缺失的 _sqlite3 和未验证的第三方依赖仍不受这些结果担保。

系列导航只列已验收文章;选修的其他解释器对照仍未执行。

参考资料