6

Python 2.7 文档的两个部分提到为扩展模块中定义的容器对象添加循环垃圾收集(CGC) 支持。

Python/C API参考手册给出了两条规则,即,

  1. 必须使用PyObject_GC_New()或分配对象的内存PyObject_GC_NewVar()
  2. 一旦所有可能包含对其他容器的引用的字段都被初始化,它必须调用PyObject_GC_Track().

而在Extending and Embedding the Python Interpreter中,Noddy例如,添加Py_TPFLAGS_HAVE_GC标志和填充tp_traversetp_clear插槽似乎足以启用 CGC 支持。上面的两条规则根本没有被实践过。

当我修改Noddy示例以实际遵循PyObject_GC_New()/PyObject_GC_Del()PyObject_Track()/的规则时PyObject_GC_UnTrack(),它出人意料地提出了断言错误说,

Modules/gcmodule.c:348: visit_decref: 断言“gc->gc.gc_refs != 0”失败。引用计数太小

这导致我对实施 CGC 的正确/安全方式感到困惑。任何人都可以提供建议,或者最好是一个具有 CGC 支持的容器对象的简洁示例吗?

4

2 回答 2

3

我本人在 C API 方面经验不足,无法给您任何建议,但 Python 容器实现本身有很多示例

就个人而言,我会先从元组实现开始,因为它是不可变的:Objects/tupleobject.c。然后转到dict,listsetimplementations 以获取有关可变容器的进一步说明:

我不禁注意到有对PyObject_GC_New(),PyObject_GC_NewVar()PyObject_GC_Track()自始至终的调用,以及Py_TPFLAGS_HAVE_GC设置。

于 2012-09-04T06:41:27.910 回答
2

在大多数正常情况下,您不需要自己进行跟踪/取消跟踪。这在文档中有所描述,但并未明确说明。在Noddy示例的情况下,您绝对不会。

简短的版本是 TypeObject 包含两个函数指针:tp_alloctp_free. 默认情况下tp_alloc,在创建类时调用所有正确的函数(如果Py_TPFLAGS_HAVE_GC已设置)并tp_free在销毁时取消跟踪类。

Noddy 文档说(在本节末尾):

差不多就是这样。如果我们编写了自定义tp_alloctp_free插槽,我们需要修改它们以进行循环垃圾收集。大多数扩展将使用自动提供的版本。

不幸的是,没有明确表明您不需要自己执行此操作的地方是支持循环垃圾收集文档


细节:

Noddy_newNoddy 是使用名为put的函数分配tp_newTypeObject。根据文档,“新”功能应该做的主要事情是调用tp_allocslot。你通常不tp_alloc自己写,它只是默认为PyType_GenericAlloc().

查看PyType_GenericAlloc()Python 源代码显示了它基于PyType_IS_GC(type). 首先它调用_PyObject_GC_Malloc而不是PyObject_Malloc,其次它调用_PyObject_GC_TRACK(obj)。[请注意,PyObject_New真正做的只是调用PyObject_Malloc然后tp_init。]

同样,在解除分配时,您调用tp_freeslotPyObject_GC_Del ,对于带有 的类,它会自动设置为Py_TPFLAGS_HAVE_GC。包括与 untrack 调用PyObject_GC_Del相同的代码。PyObject_GC_UnTrack

于 2016-11-11T08:19:19.313 回答