尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

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

发布时间:2026/9/10 1:54:40

资讯中心
01
ARTICLE

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

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()的逻辑是遍历slotdefs找出名称与被设置/删除的属性名一致的所有槽位定义借助内部字符串驻留比较见 Objects/typeobject.c#L12038 的 bpo-40521 注释若该名称与任何槽位无关如普通属性foo直接返回Objects/typeobject.c#L12051-L12052否则通过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 采用了先排队、后停止世界统一应用的策略update_slot()在遍历所有受影响类型含子类时把每个(type, slot_ptr, slot_value)三元组追加到一个更新队列中而不是立即写入队列实现为链式 chunkslot_update_chunk_t每个 chunk 固定容纳SLOT_UPDATE_CHUNK_SIZE30个条目Objects/typeobject.c#L3792结构体定义见 Objects/typeobject.c#L3794-L3803queue_slot_update()Objects/typeobject.c#L3830在 chunk 写满或队列为空时通过PyMem_Malloc分配新 chunkObjects/typeobject.c#L3808收集完成后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随后再恢复世界最后通过slot_update_free_chunks()Objects/typeobject.c#L3819释放所有 chunk。关键点在内存布局上常规路径如update_slot_after_setattrObjects/typeobject.c#L6605-L6609第一个 chunk 是在栈上分配的slot_update_chunk_t chunk {0}; slot_update_t queued_updates {chunk};只有子类数量超过SLOT_UPDATE_CHUNK_SIZE30时才需要从堆上分配后续 chunkObjects/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__ f或del cls.__repr__之类的特殊方法增删操作。验证方式可在自由线程构建下用tracemalloc或 RSS 观察反复执行上述操作后的内存增长趋势修复后内存应保持稳定。相关源码路径速查变更记录Misc/NEWS.d/next/Core_and_Builtins/2026-08-17-15-13-14.gh-issue-155978.4ztALD.rst槽位定义与更新核心Objects/typeobject.cslotdefs、update_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),仅供参考
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。