Программисты на Perl знают, как легко вызывать утечку памяти на этом языке. Достаточно создать циклическую ссылку. Этот код течёт:
while(1) {
my @p;
push @p, \\@p;
}
Аналогичный код на Python не вызывает проблем:
# всё хорошо
while True:
x = []
x.append(x)
Это происходит потому, что в Python существует достаточно полноценный сборщик мусора, а не только механизм подсчёта ссылок. Этот сборщик запускается периодически и всё подчищает.
В этом примере тоже утечек нет:
# тоже хорошо
class X:
pass
while True:
x = X()
x.prop = x
Но достаточно добавить метод __del__
и
сборщик мусора перестаёт работать,
даже если __del__
ничего не делает:
# ТЕЧЁТ
class X:
def __del__(self):
pass
while True:
x = X()
x.prop = x
Идеальный сферический пожиратель памяти в вакууме выглядит так:
# ТЕЧЁТ
class eat:
def __init__(self):
self.prop = self
def __del__(self):
pass
while True:
eat()
Утечки происходят потому, что Python не знает,
используется циклическая ссылка (в нашем случае это self.prop
)
в методе __del__
. То есть, сборщик мусора не знает
в каком месте надо начинать рвать цикл: или (i) сперва удалить
ссылку self.prop
→self
, или (ii)
сперва удалить ссылку self
→self.prop
?
Решить проблему можно в таком ключе:
# уже не течёт
import gc
class eat:
def __init__(self):
self.prop = self
# Код, занимающийся очисткой циклических ссылок.
# Форсируем сборку мусора. Каждый раз это делать
# не обязательно, здесь это сделано только для
# полноты. В боевых условиях Python сам запускает
# сборку в подходящие моменты.
gc.collect()
# пробегаем по все объектам, которые не удалось
# удалить, но доступа к которым уже нет
for t in gc.garbage:
# Строго говоря, тут надо бы поаккуратней
# проверять типы. Но в нашем простом случае,
# мы точно знаем, в чём причина проблем.
# Рвём цикл.
t.prop = None
# Отсюда наши объекты тоже надо удалить. Не забываем.
del gc.garbage[:]
def __del__(self):
pass
while True:
eat()
Успехов!