GIL, память и сборка мусора

Два вопроса про внутренности CPython: глобальная блокировка интерпретатора и то, как освобождается память.

GIL (Global Interpreter Lock) — глобальная блокировка интерпретатора CPython, из-за которой в один момент времени байткод Python исполняет только один поток.

Вопрос: что такое GIL и почему он мешает?

Чёткий ответ. GIL — это мьютекс, который не даёт двум потокам одновременно исполнять Python-байткод. Поэтому несколько потоков не ускоряют чисто вычислительные (CPU-bound) задачи: они делят одно ядро по очереди. Это особенность реализации CPython, а не языка как такового.

Важно: GIL не мешает задачам ожидания (I/O-bound) — пока поток ждёт сеть или диск, GIL отпускается, и другой поток работает. Поэтому для сетевых задач потоки полезны.

Тип задачиЧто помогает
CPU-bound (вычисления)multiprocessing — отдельные процессы, у каждого свой GIL
I/O-bound (сеть, диск)threading или asyncio — потоки эффективны на ожидании

Потоки против процессов

Потоки делят память и GIL — хороши для ожидания. Процессы изолированы, у каждого свой интерпретатор и свой GIL — значит, настоящий параллелизм на нескольких ядрах для вычислений.

# Иллюстрация распределения CPU-нагрузки между процессами (концептуально)
def cpu_task(n):
    total = 0
    for i in range(n):
        total += i * i
    return total

# 4 процесса считают независимо, каждый на своём ядре, свой GIL:
tasks = [10**6, 10**6, 10**6, 10**6]
results = [cpu_task(n) for n in tasks]
print("задач посчитано:", len(results))
print("результат одной:", results[0])

Вывод:

задач посчитано: 4
результат одной: 333332833333500000

В реальном коде эти четыре cpu_task запустили бы через multiprocessing.Pool — тогда они считались бы параллельно на разных ядрах. С потоками же из-за GIL они шли бы по очереди и ускорения бы не было.

Что отвечать про обход GIL

  • CPU-bound → multiprocessing (или нативные расширения C/numpy, отпускающие GIL).
  • I/O-bound → threading или asyncio.
  • GIL есть в CPython; в Jython/IronPython его нет, а в новых версиях CPython развивают режим без GIL.

Как Python освобождает память: подсчёт ссылок

Второй частый вопрос про внутренности — управление памятью. Основной механизм CPython — подсчёт ссылок: у каждого объекта есть счётчик ссылок на него. Присваивание увеличивает счётчик, del или выход имени из области видимости — уменьшает. Как только счётчик доходит до нуля, объект удаляется немедленно.

import sys

a = []                       # одна ссылка: a
b = a                        # теперь две ссылки
# getrefcount показывает на 1 больше — учитывает свой временный аргумент
print("ссылок после b = a:", sys.getrefcount(a) - 1)
del b                        # одну ссылку убрали
print("ссылок после del b:", sys.getrefcount(a) - 1)

Вывод:

ссылок после b = a: 2
ссылок после del b: 1

Сборщик мусора для циклических ссылок

Подсчёта ссылок мало, когда объекты ссылаются друг на друга по кругу: их счётчики не станут нулём, даже если снаружи на них никто не ссылается. Для таких циклов есть отдельный сборщик мусора (модуль gc).

import gc

class Node:
    def __init__(self):
        self.ref = None

a = Node()
b = Node()
a.ref = b                    # a -> b
b.ref = a                    # b -> a : цикл
del a
del b                        # внешних имён нет, но объекты держат друг друга

collected = gc.collect()     # сборщик циклов находит и убирает их
print("в цикле собрано объектов:", collected >= 2)

Вывод:

в цикле собрано объектов: True

Итог

  • GIL не даёт двум потокам одновременно исполнять Python-байткод: CPU-задачи ускоряют процессы (multiprocessing), а для I/O полезны потоки/asyncio.
  • Память управляется подсчётом ссылок: объект удаляется сразу, как счётчик станет нулём.
  • Циклические ссылки счётчик не ловит — их убирает отдельный сборщик gc.
Проверьте себя
1. Что такое GIL?
AСборщик мусора
BБлокировка, дающая исполнять Python-байткод только одному потоку в момент времени
CМенеджер пакетов
DТип данных
2. Что помогает ускорить CPU-bound задачу в Python?
Athreading
Bmultiprocessing (отдельные процессы)
Cбольше потоков
Dasyncio
3. Зачем нужен сборщик мусора gc, если есть подсчёт ссылок?
AДля ускорения
BЧтобы убирать циклические ссылки, которые счётчик не обнулит
CДля работы с файлами
DОн не нужен
Поддержать проект