Garbage Collection: как работает сборщик мусора в Python

В этом уроке вы узнаете, как работает сборщик мусора Python и как программно взаимодействовать с garbage collector.

Управление памятью и сборка мусора в Python

В C/C++ вы несете полную ответственность за управление памятью программы. Однако в Python вам не нужно самостоятельно управлять памятью, поскольку Python делает это за вас автоматически.

Из руководства по ссылкам вы узнали, что диспетчер памяти Python отслеживает ссылки на объекты. Диспетчер памяти уничтожает объект и освобождает память, как только счетчик ссылок этого объекта достигает нуля.

Однако подсчет ссылок не всегда работает должным образом. Например, когда у вас есть объект, который ссылается сам на себя, или два объекта ссылаются друг на друга. Это создает нечто, называемое циклическими ссылками. Когда диспетчер памяти Python не может удалить объекты с циклическими ссылками, это вызывает утечку памяти.

Вот почему сборщик мусора вступает в игру для исправления циклических ссылок. Python позволяет взаимодействовать со сборщиком мусора через встроенный модуль gc.

Взаимодействие со сборщиком мусора Python

В этом примере мы сначала создадим циклическую ссылку между двумя экземплярами класса A и класса B. Затем мы используем сборщик мусора для уничтожения объектов в циклической ссылке.

  • Сначала импортируем модули gc и ctypes, определяем две функции для подсчета ссылок и проверяем, существует ли объект в памяти:
import gc
import ctypes


def ref_count(address):
    return ctypes.c_long.from_address(address).value


def object_exists(object_id):
    for object in gc.get_objects():
        if id(object) == object_id:
            return True

    return False

В этом коде ref_count() возвращает счетчик ссылок объекта, указанного его адресом в памяти. А функция object_exists() возвращает True, если объект существует в памяти.

  • Во-вторых, создайте два класса A и B, которые ссылаются друг на друга:
class A:
    def __init__(self):
        self.b = B(self)
        print(f'A: {hex(id(self))}, B: {hex(id(self.b))}')


class B:
    def __init__(self, a):
        self.a = a
        print(f'B: {hex(id(self))}, A: {hex(id(self.a))}')
  • В-третьих, отключите сборщик мусора, вызвав функцию Disable():
gc.disable()
  • В-четвертых, создайте новый экземпляр класса A, который также автоматически создаст новый экземпляр класса B:
a = A()

Выход:

B: 0x20fccd148e0, A: 0x20fccd75c40
A: 0x20fccd75c40, B: 0x20fccd148e0
  • В-пятых, определите две переменные для хранения адресов памяти экземпляров A и B. Эти переменные отслеживают адреса памяти экземпляров A и B, когда переменная a ссылается на другой объект.
a_id = id(a)
b_id = id(a.b)
  • В-шестых, покажите количество ссылок экземпляров A и B:
print(ref_count(a_id))  # 2
print(ref_count(b_id))  # 1

Экземпляр A имеет две ссылки: переменную a и экземпляр B. И экземпляр B имеет одну ссылку, которая является экземпляром A.

  • В-седьмых, проверьте, находятся ли в памяти оба экземпляра A и B:
print(object_exists(a_id))  # True
print(object_exists(b_id))  # True

Оба они существуют.

  • В-восьмых, установите для переменной значение None:
a = None
  • В-девятых, получите счетчики ссылок экземпляров A и B:
print(ref_count(a_id))  # 1
print(ref_count(b_id))  # 1

Теперь оба счетчика ссылок экземпляра A и B равны 1.

  • В-десятых, проверьте, существуют ли экземпляры:
print(object_exists(a_id))  # True
print(object_exists(b_id))  # True

Оба они все еще существуют, как и ожидалось.

  • Одиннадцатое, запустите сборщик мусора:
gc.collect()

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

  • Двенадцатое, проверьте, существуют ли экземпляры A и B:
print(object_exists(a_id))  # False
print(object_exists(b_id))  # False

Они оба больше не существуют из-за сборщика мусора.

  • В-тринадцатых, получите счетчики ссылок экземпляров A и B:
print(ref_count(a_id))  # 0
print(ref_count(b_id))  # 0

Объединим все это вместе.

import gc
import ctypes


def ref_count(address):
    return ctypes.c_long.from_address(address).value


def object_exists(object_id):
    for object in gc.get_objects():
        if id(object) == object_id:
            return True

    return False


class A:
    def __init__(self):
        self.b = B(self)
        print(f'A: {hex(id(self))}, B: {hex(id(self.b))}')


class B:
    def __init__(self, a):
        self.a = a
        print(f'B: {hex(id(self))}, A: {hex(id(self.a))}')


# disable the garbage collector
gc.disable()

a = A()

a_id = id(a)
b_id = id(a.b)

print(ref_count(a_id))  # 2
print(ref_count(b_id))  # 1

print(object_exists(a_id))  # True
print(object_exists(b_id))  # True


a = None
print(ref_count(a_id))  # 1
print(ref_count(b_id))  # 1

print(object_exists(a_id))  # True
print(object_exists(b_id))  # True

# run the garbage collector
gc.collect()

# check if object exists
print(object_exists(a_id))  # False
print(object_exists(b_id))  # False

# reference count
print(ref_count(a_id))  # 0
print(ref_count(b_id))  # 0
Похожие посты
Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *