news 2026/9/10 1:54:31

CPython 自由线程构建中设置特殊方法引发内存泄漏的修复分析(gh-issue-155978)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
CPython 自由线程构建中设置特殊方法引发内存泄漏的修复分析(gh-issue-155978)

CPython 自由线程构建中设置特殊方法引发内存泄漏的修复分析(gh-issue-155978)

【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython

导读

本文基于 CPython 仓库中的变更记录 Misc/NEWS.d/next/Core_and_Builtins/2026-08-17-15-13-14.gh-issue-155978.4ztALD.rst 展开,聚焦一个在自由线程(free-threaded,即 Py_GIL_DISABLED)构建下出现的内存泄漏问题:当在一个拥有大量子类的类上设置或删除特殊方法(special method,如__repr__)时,解释器会泄漏内存。读完本文,你将理解 CPython 的类型槽位(type slot)更新机制、自由线程构建下"停止世界(stop-the-world)"式的槽位批量更新队列,以及该泄漏的根因与修复思路,并能据此在自己的扩展或测试中复现、规避同类问题。

变更记录原文

本次修复对应的 NEWS 条目全文如下:

Fix a memory leak in the free-threaded build when setting or deleting a special method (such as__repr__) on a class that has many subclasses.

即:修复自由线程构建中,在拥有许多子类的类上设置或删除特殊方法(如__repr__)时的内存泄漏。该条目不涉及公开 API 或行为变更,属于内部实现级的内存管理修复,归类于Core_and_Builtins

背景:特殊方法与类型槽位(type slot)

在 CPython 中,类的__repr____add____getitem__等特殊方法并不会在每次调用时都走普通的方法查找。解释器在类创建以及类属性被修改时,会把这些特殊方法"固化"到类型对象(PyTypeObject)内部的槽位指针上,例如:

  • __repr__tp_repr
  • __hash__tp_hash
  • __lt__/__le__等 6 个方法 →tp_richcompare
  • __add__/__radd__tp_as_number->nb_add

这套映射关系定义在 Objects/typeobject.c 中的slotdefs数组中。注释明确指出(见 Objects/typeobject.c#L11777-L11790):

一个槽位可能对应多个特殊方法,反之亦然。例如tp_richcompare使用__lt____ge__6 个方法,而tp_as_number->nb_add使用__add____radd__;反过来__add__同时被数值协议和序列协议使用,__getitem__同时被序列协议和映射协议使用。

槽位更新的核心逻辑在update_one_slot()(Objects/typeobject.c#L11840):

  • 沿类型的 MRO 查找对应名称的特殊方法;
  • 若找到的是匹配的wrapper_descriptor(如str.__repr__),则直接把槽位指向被包装的原生 C 函数;
  • 若找到的是普通 Python 方法,则安装一个通用 wrapper(安全但较慢,因为要走方法查找);
  • 多个特殊方法结果不一致时,安装组合 wrapper。

因此,setattr(cls, "__repr__", f)delattr(cls, "__repr__")会触发一整条槽位级联更新链,而这个链条在自由线程构建下会横跨该类的全部子类

泄漏场景:设置/删除特殊方法

type_setattro()(Objects/typeobject.c#L6630)在类的__dict__被修改后调用update_slot_after_setattr()(Objects/typeobject.c#L6601),进而调用update_slot()(Objects/typeobject.c#L12022)。

update_slot()的逻辑是:

  1. 遍历slotdefs,找出名称与被设置/删除的属性名一致的所有槽位定义(借助内部字符串驻留比较,见 Objects/typeobject.c#L12038 的 bpo-40521 注释);
  2. 若该名称与任何槽位无关(如普通属性foo),直接返回(Objects/typeobject.c#L12051-L12052);
  3. 否则通过update_subclasses(type, name, update_slots_callback, ...)(Objects/typeobject.c#L12057)把更新递归传播到所有受影响的子类

update_subclasses()借助每个类型维护的tp_subclasses字典(见 Objects/typeobject.c#L704-L794 中init_tp_subclasses()get_subclasses_unlocked()等辅助函数)遍历全部子类型。这正是泄漏与"许多子类"强相关的直接原因:子类越多,需要排队的槽位更新条目就越多

自由线程构建:队列式批量更新

在非自由线程(默认 GIL)构建中,update_slot()直接同步改写槽位指针即可,因为 GIL 保证了可见性。但自由线程构建下,槽位指针可能被其他线程并发读取,因此 CPython 采用了"先排队、后停止世界统一应用"的策略:

  1. update_slot()在遍历所有受影响类型(含子类)时,把每个(type, slot_ptr, slot_value)三元组追加到一个更新队列中,而不是立即写入;
  2. 队列实现为链式 chunk:slot_update_chunk_t每个 chunk 固定容纳SLOT_UPDATE_CHUNK_SIZE(30)个条目(Objects/typeobject.c#L3792),结构体定义见 Objects/typeobject.c#L3794-L3803;
  3. queue_slot_update()(Objects/typeobject.c#L3830)在 chunk 写满或队列为空时通过PyMem_Malloc分配新 chunk(Objects/typeobject.c#L3808);
  4. 收集完成后,apply_type_slot_updates()(Objects/typeobject.c#L3872)执行types_stop_world()停止世界,调用apply_slot_updates()把排队条目逐项写入槽位指针(期间对tp_call的更新还会清除Py_TPFLAGS_HAVE_VECTORCALL标志,见 Objects/typeobject.c#L3862-L3865),随后再恢复世界;
  5. 最后通过slot_update_free_chunks()(Objects/typeobject.c#L3819)释放所有 chunk。

关键点在内存布局上:常规路径(如update_slot_after_setattr,Objects/typeobject.c#L6605-L6609)第一个 chunk 是在栈上分配的slot_update_chunk_t chunk = {0}; slot_update_t queued_updates = {&chunk};),只有子类数量超过SLOT_UPDATE_CHUNK_SIZE(30)时才需要从堆上分配后续 chunk(Objects/typeobject.c#L6606-L6609)。

泄漏根因与修复思路

综合 NEWS 条目与源码结构可以推断,泄漏发生在堆上分配的那些溢出 chunk上:

  • 当类拥有超过 30 个子类时,update_slot()会为额外子类的槽位更新分配额外的堆 chunk;
  • 在自由线程构建的某些路径上(从源码结构看,例如update_all_slots()——即__bases__被重新赋值时的更新入口,Objects/typeobject.c#L12083-L12105——在update_slot()中途出错时仅释放了queued_updates.head指向的已挂接 chunk 链,却可能遗漏尚未挂接或已提前分配但未入队的 chunk;又如update_slot_after_setattr()的清理循环只遍历cur != &chunk的堆 chunk),部分堆内存块在异常/提前返回路径上没有被slot_update_free_chunks()回收,于是产生随"设置/删除特殊方法 + 大量子类"操作次数线性增长的泄漏;
  • 修复的核心是保证无论走正常完成路径还是错误返回路径,队列中所有堆分配的 chunk 都被完整释放,即让slot_update_free_chunks()的调用覆盖所有分配点。

由于本变更记录属于内部内存管理修复,不改变任何公开语义:设置/删除特殊方法后槽位更新行为与修复前一致,只是不再漏内存。

影响范围与验证建议

  • 受影响构建:仅自由线程构建(--disable-gil编译,即源码中#ifdef Py_GIL_DISABLED分支,见 Objects/typeobject.c#L12079)。默认 GIL 构建不受影响,因为其update_all_slots()直接同步更新、不涉及队列分配(Objects/typeobject.c#L12107-L12121)。
  • 触发条件:对一个拥有大量子类(超过SLOT_UPDATE_CHUNK_SIZE = 30个)的类反复执行cls.__repr__ = fdel cls.__repr__之类的特殊方法增删操作。
  • 验证方式:可在自由线程构建下用tracemalloc或 RSS 观察反复执行上述操作后的内存增长趋势;修复后内存应保持稳定。

相关源码路径速查

  • 变更记录:Misc/NEWS.d/next/Core_and_Builtins/2026-08-17-15-13-14.gh-issue-155978.4ztALD.rst
  • 槽位定义与更新核心:Objects/typeobject.c(slotdefsupdate_one_slot()update_slot()update_all_slots()fixup_slot_dispatchers()
  • 更新队列与停止世界应用:Objects/typeobject.c#L3780-L3903
  • 子类遍历:Objects/typeobject.c#L704-L794
  • 类型属性赋值入口:Objects/typeobject.c#L6600-L6627
  • 自由线程构建开关说明可参考仓库根目录 README.rst 中关于--disable-gil的构建说明。

【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 1:54:15

Linux权限管理详解:从rwx到ACL,彻底搞懂权限模型与排查技巧

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

作者头像 李华
网站建设 2026/9/10 1:54:12

Next.js + LangChain.js:前端工程师的AI工程化实战路径

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

作者头像 李华
网站建设 2026/9/10 1:52:30

IMM-UKF三维目标跟踪:解决模型不确定性与非线性观测

简介:本资源是一套基于MATLAB实现的三维目标路径预测与跟踪仿真代码,面向控制工程、导航定位及智能感知领域的研究者与高年级本科生,解决非线性、多运动模态下动态目标实时估计精度低的问题。代码融合交互式多模型(IMM&#xff09…

作者头像 李华
网站建设 2026/9/10 1:52:09

Malware Analysis Report

Malware Analysis Report 【免费下载链接】agents Multi-harness agentic plugin marketplace for Claude Code, Codex, Cursor, OpenCode, GitHub Copilot, and Google Antigravity 项目地址: https://gitcode.com/GitHub_Trending/agents24/agents Executive Summary …

作者头像 李华
网站建设 2026/9/10 1:51:02

C++用ODBC连接MySQL 8.0完整指南:环境搭建与代码实战

简介:一份面向C开发者的MySQL ODBC连接示例工程,适合需要掌握ODBC标准接口或在Visual Studio中集成数据库操作的初学者。资源为一个Visual Studio项目压缩包,共26个文件,包含8个头文件(.h)、7个C源文件&…

作者头像 李华