1

В CPython У меня есть два типа объектов, которые тесно связаны друг с другом.Как разбить прямой ссылочный цикл в 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, которое, как я подозреваю, является плохой практикой. Теперь я попытался использовать сборщик мусора здесь, но все же объекты не собираются.

Что здесь общего не работает? Что я упустил?

+0

я обновил вопрос, который реализует интерфейс сборщика мусора; еще нет сбор. – tynn

ответ

0

Хорошо, я наконец-то нашел мою проблему. Я не начал отслеживать с 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 
0

Вы можете сделать это, используя слабые ссылки (см. Модуль weakref). Но обычно лучше просто полагаться на сборщика мусора. Возможно, кто-то другой создаст большой ссылочный цикл с вашими объектами, и тогда вы все равно будете полагаться на GC, поэтому вы можете использовать его для простого случая.

Пожалуйста, объясните, что вы имеете в виду под «неудачей».

+0

Мне нужны эти сильные ссылки, так как оба объекта должны выживать вместе. Наверное, я плохо сработал с GC, потому что я не понимал концепцию пересечения и очистки правильно или сделал ошибку, которую я не видел. Я попробую еще раз. – tynn

0

Важная вещь, которую следует учитывать в (большинстве) сборных мусора, заключается в том, что удаление объекта не гарантируется, как только объект становится недоступным. Как только объект становится недоступным, он полностью зависит от сборщика мусора относительно того, когда он освободит связанные ресурсы, что может быть до конца, когда программа закончится, если нет давления на память.

Если вы не установили методы __del__ для связанных классов, сборщик мусора должен работать нормально. Он не будет немедленно очищать ваши объекты, поскольку функция обнаружения эталонных циклов является более дорогостоящей, чем простой подсчет ссылок, и, как таковая, выполняется нечасто.

Пример с использованием чистого питона класса

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 
+0

CPython является особенным; все объекты подсчитываются по ссылке, и GC теоретически необязателен (но AFAIK никто в здравом уме фактически не отключает его). – Kevin

Смежные вопросы