GIL и free-threading (PEP 703)
| GIL | мьютекс, разрешающий выполнять байт-код Python только одному потоку |
|---|---|
| Появилась | в CPython с поддержкой потоков (1992) |
| PEP 703 | Сэм Гросс (Meta), принят в 2023 году |
| Python 3.13 | экспериментальная сборка без GIL (python3.13t) |
| Python 3.14 | сборка без GIL официально поддерживается (PEP 779) |
| Альтернативы | multiprocessing, субинтерпретаторы, asyncio, расширения на C и Rust |
GIL (англ. Global Interpreter Lock — глобальная блокировка интерпретатора) — механизм в CPython, основной реализации Python, который разрешает выполнять байт-код Python только одному потоку в каждый момент времени. Даже на 16-ядерном процессоре несколько потоков Python, занятых вычислениями, работают по очереди, а не одновременно. Free-threading («свободная многопоточность») — сборка CPython без GIL, ставшая возможной благодаря PEP 703: в ней потоки выполняют Python-код по-настоящему параллельно.[1]
Зачем был нужен GIL
CPython управляет памятью с помощью подсчёта ссылок: у каждого объекта есть счётчик, и объект удаляется, когда счётчик падает до нуля. Если два потока одновременно меняют счётчик без синхронизации, он испортится — объект удалится раньше времени или никогда. GIL, появившийся в 1992 году вместе с поддержкой потоков, решал это просто: один замок на весь интерпретатор. Это давало быстрый однопоточный код, простую реализацию и лёгкое написание расширений на C, которые могли не думать о потокобезопасности.
Цена — невозможность ускорить вычисления потоками. Попытки убрать GIL предпринимались с конца 1990-х, но каждая замедляла однопоточные программы, и их отвергали.
Когда GIL мешает, а когда нет
| Задача | Влияние GIL |
|---|---|
| Ожидание сети, диска, базы данных | почти не мешает: на время ввода-вывода GIL отпускается |
| Вычисления в NumPy, PyTorch, сжатие, хеширование | не мешает: код на C отпускает GIL на время работы |
| Вычисления на чистом Python в потоках | мешает: потоки выполняются по очереди |
Поэтому для веб-серверов и парсеров потоки и asyncio работают хорошо, а для тяжёлых вычислений на Python традиционно использовали процессы (см. Конкурентность и параллелизм).
PEP 703
В 2021–2023 годах Сэм Гросс из Meta показал прототип CPython без GIL с приемлемыми потерями производительности. Ключевые приёмы:
- Смещённый подсчёт ссылок (biased reference counting): поток-владелец меняет счётчик объекта быстро, без атомарных операций, а остальные потоки — через отдельный атомарный счётчик.
- Бессмертные объекты: у
None,True, малых чисел счётчик не меняется вовсе. - Аллокатор mimalloc и блокировки на уровне отдельных объектов вместо одной глобальной.
В октябре 2023 года руководящий совет Python принял PEP 703 с поэтапным планом: сначала экспериментальная сборка, затем официальная поддержка и, если экосистема будет готова, — переход по умолчанию.
| Версия | Статус |
|---|---|
| 3.13 (2024) | экспериментальная сборка без GIL, заметное замедление однопоточного кода[4] |
| 3.14 (2025) | официально поддерживаемая сборка (PEP 779), замедление сокращено[2] |
| будущее | возможный переход на сборку без GIL по умолчанию |
Как попробовать
uv python install 3.14t # сборка без GIL (суффикс t — threaded)
uv run --python 3.14t python -c "import sys; print(sys._is_gil_enabled())" # False
import threading, time
def work(n: int) -> int:
return sum(i * i for i in range(n))
start = time.perf_counter()
threads = [threading.Thread(target=work, args=(10_000_000,)) for _ in range(4)]
for t in threads: t.start()
for t in threads: t.join()
print(f"{time.perf_counter() - start:.1f} с")
# с GIL — примерно как 4 последовательных вызова; без GIL — заметно быстрее на 4 ядрах
Если в процесс загружается расширение на C, не объявившее поддержку free-threading, интерпретатор по умолчанию включает GIL обратно и предупреждает об этом; переменная окружения PYTHON_GIL=0 позволяет принудительно отключить его.[3]
Что меняется для разработчиков
- Гонки данных становятся реальностью. Код, «случайно» корректный благодаря GIL, может сломаться: общий счётчик, словарь-кэш, ленивая инициализация требуют блокировок (
threading.Lock) или потокобезопасных структур (см. Состояние гонки и блокировки). Встроенные типы —list,dict— защищены от повреждения, но последовательность операций над ними атомарной не становится. - Расширения на C и Rust должны быть проверены и пересобраны под новую сборку; сообщество отслеживает готовность популярных пакетов.[6]
- Однопоточный код в сборке без GIL работает немного медленнее.
Альтернативы
- multiprocessing и ProcessPoolExecutor — отдельные процессы, каждый со своим GIL; надёжно, но данные приходится сериализовать.
- Субинтерпретаторы — несколько интерпретаторов в одном процессе, каждый со своим GIL (PEP 684, Python 3.12);[5] в Python 3.14 появился модуль
concurrent.interpreters. - asyncio — для задач с ожиданием ввода-вывода GIL не помеха.
- Вынос горячего кода в NumPy, Numba, Cython или расширения на Rust, отпускающие GIL.