新闻详情

新闻详情

首页 / 资讯中心 / 详情

CPython free-threaded 构建中的数据竞争修复:gc.get_count 年轻代分配计数的原子化改造

发布时间:2026/9/10 6:08:35来源:尧图网络
CPython free-threaded 构建中的数据竞争修复:gc.get_count 年轻代分配计数的原子化改造
CPython free-threaded 构建中的数据竞争修复gc.get_count 年轻代分配计数的原子化改造【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython导读在 CPython 的 free-threaded无 GIL构建中多个线程可以同时分配对象并更新 GC垃圾回收的年轻代分配计数gc.get_count()在读取该计数时可能与写入方发生数据竞争。本篇文章以 CPython 仓库中的 news 条目 2026-05-26-00-06-30.gh-issue-150411.u-d-_5.rst 为切入点结合 Modules/gcmodule.c、Include/internal/pycore_tstate.h 与 Lib/test/test_gc.py 的源码实现深入剖析该数据竞争的成因、修复方案线程本地分配计数 原子刷新及其对 GC 语义的影响。读完本文你将理解 free-threaded 构建下 GC 状态字段的并发保护策略以及如何在多线程分配场景中正确解读gc.get_count()的返回值。一、修复背景free-threaded 构建下的 GC 计数竞争1.1 news 条目原文本次修复由 CPython 开发者提交到Misc/NEWS.d/next/Core_and_Builtins/目录作为开发分支的 news fragment发布时会被合并进版本更新说明。原文如下Fix a data race in the free-threaded build whengc.get_countreads the young generation allocation count while another thread updates it.它明确描述了两个事实发生场景free-threaded 构建编译时启用Py_GIL_DISABLED即无 GIL 的并发解释器问题本质gc.get_count读取年轻代young generation分配计数时另一个线程正在并发更新该计数形成典型的读写数据竞争data race。在启用 GIL 的常规构建中解释器执行任何字节码时都持有 GILGC 计数器的读写天然互斥因此不存在此问题而 free-threaded 构建去掉了这把全局锁GC 内部的所有共享状态都必须显式引入并发防护。1.2 为什么年轻代分配计数会成为竞争热点在 free-threaded 构建中CPython 的 GC 采用年轻代 老年代两级结构源码见 Modules/gcmodule.c 中gc_get_count_impl的Py_GIL_DISABLED分支。每次新建可被 GC 追踪的对象如容器、自定义类实例时都会递增年轻代的分配计数当计数超过阈值gc.set_threshold设定的 threshold0时触发一次年轻代回收。由于 free-threaded 构建中任意线程都可能随时分配对象年轻代计数是一个被高频并发写入的共享变量。如果gc.get_count()用普通非原子读去访问它轻则读到撕裂的中间值重则触发 C 语言层面的未定义行为——这正是本次修复要消除的隐患。二、修复前的隐患共享计数器缺少并发防护虽然本次 news 条目没有给出修复前的旧代码但从当前源码中仍可反推竞争点的位置与性质写入方每个线程在分配 GC 追踪对象时更新年轻代计数读取方任意线程调用gc.get_count()时直接读取该计数。在 GIL-enabled 构建中gc_get_count_impl通过#ifndef Py_GIL_DISABLED分支直接返回gcstate-generations[0].count等字段见 Modules/gcmodule.cGIL 保证了读写串行化。而在 free-threaded 构建中没有任何隐式锁保护这个计数器读写之间必须依赖原子操作——这正是修复的核心。三、修复方案剖析线程本地计数 原子刷新从当前源码结构看修复采用了两层机制同时降低了原子操作的频率并消除了数据竞争。3.1 第一层线程本地分配计数减少全局原子写Include/internal/pycore_tstate.h 中定义了每个线程私有的 GC 状态结构#ifdef Py_GIL_DISABLED struct _gc_thread_state { /* Thread-local allocation count. */ Py_ssize_t alloc_count; }; #endif每个PyThreadState实际分配为_PyThreadStateImpl都内嵌一个struct _gc_thread_state gc字段同文件第 71 行。free-threaded 构建下各线程先在自己私有的alloc_count上累计分配次数而不是每次分配都去修改全局共享计数。这样绝大多数分配路径完全不触碰共享变量从根本上减少了竞争窗口。3.2 第二层读取时原子刷新消除读写竞争gc.get_count()的实现位于 Modules/gcmodule.cstatic PyObject * gc_get_count_impl(PyObject *module) { GCState *gcstate get_gc_state(); #ifdef Py_GIL_DISABLED _PyThreadStateImpl *tstate (_PyThreadStateImpl *)_PyThreadState_GET(); struct _gc_thread_state *gc tstate-gc; // Flush the local allocation count to the global count _Py_atomic_add_int(gcstate-young.count, (int)gc-alloc_count); gc-alloc_count 0; #endif #ifndef Py_GIL_DISABLED return Py_BuildValue((iii), gcstate-generations[0].count, gcstate-generations[1].count, gcstate-generations[2].count); #else return Py_BuildValue((iii), _Py_atomic_load_int_relaxed(gcstate-young.count), gcstate-old[0].count, gcstate-old[1].count); #endif }修复体现在Py_GIL_DISABLED分支的两步操作上原子刷新flush调用_Py_atomic_add_int(gcstate-young.count, (int)gc-alloc_count)把当前线程本地累计的分配次数以原子加的方式并入全局年轻代计数随后将本地计数清零。_Py_atomic_add_int保证读取-相加-写回整体不可分割写入方与读取方不再撕裂原子读取通过_Py_atomic_load_int_relaxed(gcstate-young.count)读取全局年轻代计数。relaxed内存序只要求读操作本身的原子性不撕裂、不读到中间状态不额外引入跨线程顺序约束——因为gc.get_count()本就不承诺严格一致的快照语义这样的取舍在保证无竞争的同时把开销降到最低。对照 GIL-enabled 构建generations[0..2].count可以看到free-threaded 构建将三代结构压缩为年轻代 两个老年代且老年代计数old[0].count、old[1].count仍为普通字段说明它们的更新路径在 free-threaded 下另有同步机制如 Stop-the-World 回收竞争风险主要集中于被高频更新的年轻代计数。3.3 同步原语的选择依据_Py_atomic_add_int/_Py_atomic_load_int_relaxed属于 CPython 内部统一的原子操作接口定义于 Include/pyatomic.h在不同平台分别映射到 C11 原子、GCC 内建原子或 MSVC 的Interlocked系列从而保证跨平台的无竞争语义。四、横向对照free-threaded 构建下 GC 状态的多种并发防护本次修复并非孤例。在 free-threaded 构建下Modules/gcmodule.c 针对不同 GC 状态字段采用了差异化的防护策略理解这些对照有助于把握本次修复的定位GC 状态操作并发防护手段源码位置年轻代分配计数gc.get_count读取线程本地计数 原子加 / 原子 relaxed 读Modules/gcmodule.c阈值设置gc.set_threshold_PyEval_StopTheWorld/_PyEval_StartTheWorld全停世界Modules/gcmodule.c各代统计快照gc.get_statsPyMutex_Lock(gcstate-stats_mutex)互斥锁Modules/gcmodule.c可以看出 CPython 的并发设计原则高频、细粒度的计数更新用原子操作 线程本地缓冲低频、结构性的状态变更用 Stop-the-World 或互斥锁。本次对gc.get_count的修复属于前一类——既要保证读端无竞争又不能给每次分配路径引入锁开销。gc.get_stats的实现还特别注释了To get consistent values despite allocations while constructing the result list, we use a snapshot为避免构造结果期间因分配导致统计不一致而使用快照进一步印证了 free-threaded 下 GC 统计接口对一致性问题的系统性处理思路。五、测试验证与语义边界5.1 既有测试对 free-threaded 计数的特殊处理Lib/test/test_gc.py 中的test_get_count精确断言了分配前后计数递增关系refcount_test requires_gil_enabled(needs precise allocation counts) def test_get_count(self): gc.collect() a, b, c gc.get_count() x [] d, e, f gc.get_count() self.assertEqual((b, c), (0, 0)) self.assertEqual((e, f), (0, 0)) ... self.assertGreater(d, a)注意其装饰器requires_gil_enabled(needs precise allocation counts)该测试要求精确的分配计数因此在 free-threaded 构建下会被跳过。原因正与本次修复的机制相关——free-threaded 下计数先积累在线程本地alloc_count中只有调用gc.get_count()时才刷新到全局再加上 relaxed 原子读不保证强一致快照返回值是一个尽力而为的近似值无法满足精确断言。5.2 结构差异的佐证测试文件另有多处注释确认 free-threaded 构建的 GC 结构差异例如 Lib/test/test_gc.py 指出 The free-threaded build doesnt have multiple generations与gc_get_count_impl中young / old[0] / old[1]的三元组返回结构完全对应——即 free-threaded 构建只有年轻代加两级老年代而非传统构建的三代模型。六、对使用者的意义对普通 Python 开发者而言本次修复不改变gc.get_count()的公开 API 语义返回(count0, count1, count2)三元组其价值主要体现在正确性消除了 free-threaded 构建下gc.get_count()与分配线程之间的未定义行为读到的计数不再可能是撕裂的中间值性能通过线程本地缓冲把全局原子操作从每次分配一次降为每次调用 get_count 一次避免高频分配路径上的原子写竞争语义提示在 free-threaded 构建中gc.get_count()的年轻代计数代表截至调用时刻已刷新的近似分配量不宜用于需要精确计数的诊断场景——gc.get_stats()提供的各代回收统计collections / collected / uncollectable / candidates / duration更适合做调优参考。七、延伸阅读news 条目原文Misc/NEWS.d/next/Core_and_Builtins/2026-05-26-00-06-30.gh-issue-150411.u-d-_5.rstgc模块入口与各代计数实现Modules/gcmodule.c线程本地 GC 状态结构定义Include/internal/pycore_tstate.hGC 行为测试含 free-threaded 相关跳过条件Lib/test/test_gc.py原子操作接口Include/pyatomic.hfree-threaded 解释器设计文档InternalDocs/interpreter.md【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

北斗短报文:破解远海“失联”难题的实战指南 2026/9/10 6:53:42

北斗短报文:破解远海“失联”难题的实战指南

出港三小时,船舷两侧只剩海水的颜色,手机信号栏从满格掉到一格,再变成无服务。跑远海的都懂,这个“失联”不是心理上的,是物理上的——离岸超过三四十公里,陆地基站的信号就够不着你了。以前我们叫这段路“…

阅读更多 →
AI创业公司的真正护城河:数据闭环、工程化能力与行业深耕 2026/9/10 6:53:42

AI创业公司的真正护城河:数据闭环、工程化能力与行业深耕

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
OpenViking 资源管理命令实战指南:ov CLI 下的增删改查、定时刷新与语义检索 2026/9/10 6:53:42

OpenViking 资源管理命令实战指南:ov CLI 下的增删改查、定时刷新与语义检索

OpenViking 资源管理命令实战指南:ov CLI 下的增删改查、定时刷新与语义检索 【免费下载链接】OpenViking Self-evolving Context Database for AI Agents. Unify Agent Memory, Knowledge RAG and Skills. 项目地址: https://gitcode.com/GitHub_Trending/op/Ope…

阅读更多 →
CANN/ge ACL模型数据集缓冲区添加API 2026/9/10 6:53:42

CANN/ge ACL模型数据集缓冲区添加API

aclmdlAddDatasetBuffer 【免费下载链接】ge GE(Graph Engine)是面向昇腾的图编译器和执行器,提供了计算图优化、多流并行、内存复用和模型下沉等技术手段,加速模型执行效率,减少模型内存占用。 GE 提供对 PyTorch、Te…

阅读更多 →
去AI味实操指南:从词汇降频到结构留白,让AI文本回归人性 2026/9/10 6:53:42

去AI味实操指南:从词汇降频到结构留白,让AI文本回归人性

拿到一篇AI生成的初稿,读两遍之后总觉得哪里不对劲:句子都通顺,逻辑也没毛病,可就是透着一股“机器味”——像一杯白开水,无懈可击,但也寡淡无味。这就是我最近一直在死磕的问题,也是“humanize…

阅读更多 →
{产品 / 功能名} 2026/9/10 6:50:42

{产品 / 功能名}

{产品 / 功能名} 【免费下载链接】ECC The agent harness performance optimization system. Skills, instincts, memory, security, and research-first development for Claude Code, Codex, Opencode, Cursor and beyond. 项目地址: https://gitcode.com/GitHub_Trending/…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞