Имеет ли Python GC такие циклы ссылок? - PullRequest
14 голосов
/ 06 ноября 2011

Используя objgraph , я нашел несколько таких объектов:

InstanceState loop

Будет ли сборщик мусора в Python иметь дело с подобными циклами или будет течь?

немного более широкий вид петли:

Wider view of InstanceState loop

Ответы [ 4 ]

25 голосов
/ 06 ноября 2011

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

Дополнительное средство для сбора мусора , однако, включено по умолчанию и должно иметь возможность освободить эту структуру, если ни один из его компонентов больше недоступен извне и у них нет __del__() методы .

Если они это сделают, сборщик мусора не не освободит их , поскольку он не может определить безопасный порядок для выполнения этих __del__() методов.

19 голосов
/ 06 ноября 2011

Чтобы немного расширить ответ Фредерика, , раздел "подсчет ссылок" в документах хорошо объясняет обнаружение дополнительного цикла.

Поскольку я нахожу, что объяснение является хорошим способом подтверждения, я понимаю, вот несколько примеров ... С этими двумя классами:

class WithDel(object):
    def __del__(self):
        print "deleting %s object at %s" % (self.__class__.__name__, id(self))


class NoDel(object):
    pass

Создание объекта и потеря ссылки из a запускает метод __del__, благодаря подсчету ссылок:

>>> a = WithDel()
>>> a = None  # leaving the WithDel object with no references 
deleting WithDel object at 4299615184

Если мы создадим эталонный цикл между двумя объектами без метода __del__, все будет без утечек, на этот раз благодаря обнаружению цикла. Сначала включите вывод отладочной информации сборки мусора:

>>> import gc
>>> gc.set_debug(gc.DEBUG_COLLECTABLE | gc.DEBUG_UNCOLLECTABLE | gc.DEBUG_OBJECTS)

Затем создайте цикл ссылок между двумя объектами:

>>> a = NoDel(); b = NoDel()
>>> a.other = b; b.other = a  # cyclical reference
>>> a = None; b = None # Leave only the reference-cycle
>>> gc.collect()
gc: collectable <NoDel 0x10046ed50>
gc: collectable <NoDel 0x10046ed90>
gc: collectable <dict 0x100376c20>
gc: collectable <dict 0x100376b00>
4
>>> gc.garbage
[]

(dict от внутренних объектов __dict__)

Все в порядке, до , даже если один из объектов в цикле содержит __del__ метод:

>>> a = NoDel(); b = WithDel()
>>> a.other = b; b.other = a
>>> a = None; b = None
>>> gc.collect()
gc: uncollectable <WithDel 0x10046edd0>
gc: uncollectable <dict 0x100376b00>
gc: uncollectable <NoDel 0x10046ed90>
gc: uncollectable <dict 0x100376c20>
4
>>> gc.garbage
[<__main__.WithDel object at 0x10046edd0>]

Как упоминал Павел, цикл можно разорвать с помощью weakref:

>>> import weakref
>>> a = NoDel(); b = WithDel()
>>> a.other = weakref.ref(b)
>>> b.other = a # could also be a weakref

Затем, когда ссылка b на объект WithDel потеряна, он удаляется, несмотря на цикл:

>>> b = None
deleting WithDel object at 4299656848
>>> a.other
<weakref at 0x10045b9f0; dead>

О, объект мог бы указать на проблемный __del__ метод, подобный этому

5 голосов
/ 06 ноября 2011

Python GC предназначен для обхода всех живых объектов с целью обнаружения и устранения циклов ссылок без внешних ссылок.

Вы можете проверить, что происходит, запустив gc.collect(), а затем напечатав gc.garbage и gc.get_objects .

2 голосов
/ 06 ноября 2011

Если вы используете слабые ссылки для родительских указателей, то GC будет происходить нормально.

...