Корректный циклический сбор мусора в модулях расширения

два раздела документа Python 2.7 упомянули добавление циклический сбор мусора (CGC) поддержка контейнерных объектов, определенных в модулях расширения.

на справочное руководство по API Python/C дает два правила, т. е.

  1. память для объекта должна быть выделена с помощью PyObject_GC_New() или PyObject_GC_NewVar().
  2. после инициализации всех полей, которые могут содержать ссылки на другие контейнеры, он должен вызвать PyObject_GC_Track().

а в расширение и внедрение интерпретатора Python на Noddy например, кажется, что добавление Py_TPFLAGS_HAVE_GC флаг и заполнения tp_traverse и tp_clear слотов было бы достаточно, чтобы включить поддержку CGC. И два вышеприведенных правила вообще не практикуются.

когда я изменил Noddy пример, чтобы на самом деле следовать правилам PyObject_GC_New()/PyObject_GC_Del() и PyObject_Track()/PyObject_GC_UnTrack(), это удивительно подняло ошибку утверждения говоря:

модули/gcmodule.c: 348: visit_decref: утверждение " gc - >gc.gc_refs != 0" не удалось. счетчик был слишком мал

это приводит к моей путанице о правильном / безопасном способе реализации CGC. Может ли кто-нибудь дать советы или, предпочтительно,опрятный пример объекта контейнера с поддержкой CGC?

2 ответов


в большинстве нормальных обстоятельств вам не нужно делать отслеживание / отследить себя. Это описано в документации, однако это не сделано специально очистить. В случае Noddy пример определенно нет.

короткая версия заключается в том, что TypeObject содержит два указателя функции: tp_alloc и tp_free. По умолчанию tp_alloc вызывает все правильные функции при создании класса (если Py_TPFLAGS_HAVE_GC) и tp_free untracks в класс по разрушению.

на документация Noddy говорит (в конце секции):

это почти все. Если бы мы написали на заказ tp_alloc или tp_free слоты, нам нужно будет изменить их для циклической сборки мусора. Большинство расширений будут использовать версии автоматически.

к сожалению, единственное место, не дать понять, что вам не нужно делать это самостоятельно-это поддержка Циклическая документация по сбору мусора.


деталь:

Noddy's выделяются с помощью функции Noddy_new положить в tp_new слоты TypeObject. Согласно документация, главное, что должна сделать" новая " функция, это вызвать tp_alloc слот. Вы обычно не пишете 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_free слот, который автоматически устанавливается в PyObject_GC_Del классы Py_TPFLAGS_HAVE_GC. PyObject_GC_Del включает код, который делает то же самое, что и PyObject_GC_UnTrack Итак, вызов untrack не нужен.


Я недостаточно опытен в API C сам, чтобы дать вам какие-либо советы, но есть много примеры в самих реализациях контейнера Python.

лично я бы сначала начал с реализации кортежа, так как он неизменен: объекты/tupleobject.c. Затем перейдите к dict, list и set реализации для дальнейших заметок о mutable контейнеры:

Я не могу не заметить, что есть звонки на PyObject_GC_New(), PyObject_GC_NewVar() и PyObject_GC_Track() во всем, а также имея Py_TPFLAGS_HAVE_GC set.