【问题标题】:How to break a direct reference cycle in CPython如何在 Python 中打破直接引用循环
【发布时间】:2014-11-17 21:34:09
【问题描述】:

在 CPython 中,我有两种类型的对象,它们彼此紧密相连。

#include <Python.h>
#include <structmember.h>

typedef struct pyt PYT;
struct pyt { PyObject_HEAD PYT *other; };

static void dealloc (PYT *self) {
    Py_CLEAR(self->other);
    printf("dealloc object at %p\n", self);
    PyObject_GC_Del(self);
}

static PyTypeObject Pyt2Type = {
    PyObject_HEAD_INIT(NULL)
    0, "pyt.Pyt2", sizeof(PYT), 0,
    (destructor) dealloc
};

static PyObject * new (PyTypeObject *type, PyObject *args, PyObject *kwds) {
    PYT *self = PyObject_GC_New(PYT, type);
    if (!self) return NULL;
    self->other = PyObject_GC_New(PYT, &Pyt2Type);
    if (!self->other) { Py_DECREF(self); return NULL; }
    return Py_INCREF(self), self->other->other = self, (PyObject *) self;
}

static PyTypeObject Pyt1Type = {
    PyObject_HEAD_INIT(NULL)
    0, "pyt.Pyt1", sizeof(PYT), 0,
    (destructor) dealloc
};

static int traverse (PYT *self, visitproc visit, void *arg) {
    Py_VISIT(self->other);
    return 0;
}

static int clear (PYT *self) {
    Py_CLEAR(self->other);
    return 0;
}

static PyMemberDef members[] = {
    {"other", T_OBJECT, offsetof(PYT, other), RO, "other"},
    { NULL }
};

static PyMethodDef methods[] = {{ NULL }};

PyMODINIT_FUNC initpyt ( void ) {
    PyObject* m;

    Pyt1Type.tp_flags = Pyt2Type.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC;
    Pyt1Type.tp_traverse = Pyt2Type.tp_traverse = (traverseproc) traverse;
    Pyt1Type.tp_clear = Pyt2Type.tp_clear = (inquiry) clear;
    Pyt1Type.tp_members = Pyt2Type.tp_members = members;
    Pyt1Type.tp_new = new;

    if (PyType_Ready(&Pyt1Type) < 0) return;
    if (PyType_Ready(&Pyt2Type) < 0) return;

    m = Py_InitModule("pyt", methods);

    Py_INCREF(&Pyt1Type), PyModule_AddObject(m, "Pyt", (PyObject *) &Pyt1Type);
}

使用我的测试脚本

from distutils.core import Extension, setup
import sys, gc
sys.argv.extend(["build_ext", "-i"])
setup(ext_modules = [Extension('pyt', ['pyt.c'])])
from pyt import Pyt
pyt = Pyt()
print pyt, sys.getrefcount(pyt)
pyt = pyt.other
print pyt, sys.getrefcount(pyt)
del pyt
gc.collect()

我得到类似的输出

<pyt.Pyt1 object at 0x7fbc26540138> 3
<pyt.Pyt2 object at 0x7fbc26540150> 3

对象最终不会被删除,因为每个对象都保持对另一个对象的引用,从而创建了一个封闭的循环。在其他代码中,我使用了一种方法,我只保留对象,直到两者的引用计数均为 0,我怀疑这是不好的做法。现在我在这里尝试使用垃圾收集器,但仍然没有收集到对象。

这里出了什么问题?我错过了什么?

【问题讨论】:

  • 我更新了问题,实现了垃圾收集器接口;仍然没有收藏。

标签: python python-2.7 python-c-api cpython reference-counting


【解决方案1】:

您可以使用弱引用来做到这一点(请参阅weakref 模块)。但通常最好只依赖垃圾收集器。其他人可能会创建一个涉及您的对象的大型引用循环,然后您仍然会依赖 GC,因此您不妨将它用于简单的情况。

请解释一下“严重失败”是什么意思。

【讨论】:

  • 我需要这些强引用,因为两个对象都需要一起生存。我想我在 GC 上失败得很惨,因为我没有正确理解遍历和清除的概念,或者犯了一个我没有看到的错误。我会再试一次。
【解决方案2】:

关于(大多数)垃圾收集语言需要注意的重要一点是,不能保证在对象变得无法访问时立即删除对象。一旦一个对象变得不可访问,它完全取决于垃圾收集器何时释放相关资源,如果没有内存压力,这可能会延迟到程序结束时。

如果您没有为链接类设置__del__ 方法,那么垃圾收集器应该可以正常工作。它不会立即清理您的对象,因为检测引用周期的功能比简单的引用计数更昂贵,因此不经常运行。

使用纯 python 类的示例

import gc
import weakref

class Obj(object): pass

x = Obj()
y = Obj()

x.y = y, y.x = x

ref = weakref.ref(x)

print(ref())
del x, y
print(ref())
gc.collect()
print(ref())

输出:

<__main__.Obj object at 0x7f81c8ccc7b8>
<__main__.Obj object at 0x7f81c8ccc7b8>
None

【讨论】:

  • CPython 很特别;所有对象都被引用计数,GC 理论上是可选的(但 AFAIK 没有人在他们正确的头脑中实际上将其关闭)。
【解决方案3】:

好的,我终于找到了我的问题。我没有从 PyObject_GC_Track 开始跟踪。

在使用垃圾收集器时,Python 需要一些步骤:

  • Py_TPFLAGS_HAVE_GC 添加到tp_flags
  • 添加一个tp_traverse,如果需要,添加一个tp_clear函数
  • 使用PyObject_GC_New 或类似函数创建对象
  • 在完全初始化的对象上调用PyObject_GC_Track
  • 使用PyObject_GC_Del 或类似函数删除对象

所以这里修改new函数就足够了。

static PyObject * new (PyTypeObject *type, PyObject *args, PyObject *kwds) {
    PYT *self = PyObject_GC_New(PYT, type);
    if (!self) return NULL;
    self->other = PyObject_GC_New(PYT, &Pyt2Type);
    if (!self->other) { Py_DECREF(self); return NULL; }
    self->other->other = (Py_INCREF(self), self);
    PyObject_GC_Track((PyObject *) self);
    PyObject_GC_Track((PyObject *) self->other);
    return (PyObject *) self;
}

输出为

<pyt.Pyt1 object at 0x7f4904fe1398> 4
<pyt.Pyt2 object at 0x7f4904fe15c8> 4
dealloc object at 0x7f4904fe15c8
dealloc object at 0x7f4904fe1398

【讨论】:

    猜你喜欢
    • 2022-11-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-12-07
    • 1970-01-01
    • 2015-01-21
    相关资源
    最近更新 更多