# Глава 29. GIL
Кто-то называет GIL (global interpreter lock) главной архитектурной ошибкой питона. Кто-то — неизбежным компромиссом между скоростью выполнения однопоточных и многопоточных скриптов. А кто-то — вполне удачным решением, от которого бессмысленно отказываться.
Разберем, что такое GIL, откуда у него растут ноги, какие на него планы у мейнтейнеров языка и что с этим делать простым разработчикам.
## Что такое GIL
GIL, то есть глобальная блокировка интерпретатора, гарантирует, что в каждый момент времени байт-код скрипта исполняется только одним потоком ОС. Из-за GIL питон лишен истинной параллельности выполнения разных потоков. Даже на многоядерных CPU. При этом GIL не препятствует параллельности выполнения разных процессов: на каждый процесс скрипта запускается отдельный процесс интерпретатора со своим GIL.
Для чего нужен GIL? При выполнении кода интерпретатор работает с потоко-небезопасными переменными. Чтобы гарантировать их сохранность, каждый поток интерпретатора должен захватывать и отпускать GIL. К таким переменным, например, относится счетчик ссылок.
Кстати, значение счетчика ссылок можно посмотреть для каждого объекта.
```python {.example_for_playground}
from sys import getrefcount
d = {}
d2 = d
print(getrefcount(d))
```
```
3
```
В примере у пустого словаря количество ссылок равно 3: на него ссылаются переменные `d` и `d2`, но откуда третья ссылка? Возвращаемое `getrefcount()` количество как правило на единицу больше, чем ожидается. Оно учитывает временную ссылку в качестве аргумента самой функции `getrefcount()`.
Закономерно возникает вопрос: нельзя ли вместо блокирования потока целиком блокировать каждую потоко-небезопасную переменную отдельно? Синхронизация доступа к отдельным объектам вместо глобальной блокировки приводит к частым захватам/освобождениям этих самых блокировок. А это примерно [на 30%](https://docs.python.org/3/faq/library.html#can-t-we-get-rid-of-the-global-interpreter-lock) замедляет однопоточные скрипты. Поэтому разработчики языка сделали выбор в пользу глобальной блокировки.
Пара важных фактов о GIL:
- GIL не является частью языка. Это особенность реализации интерпретатора. Он есть в стандартном интерпретаторе CPython, написанном на C. Также он есть в [PyPy,](https://www.pypy.org/) написанном на языке RPython. Но GIL отсутствует в таких интерпретаторах как [Jython](https://www.jython.org/) (написан на Java) и [IronPython](https://ironpython.net/) (написан на C#). Там все проблемы синхронизации делегируются виртуальным машинам JVM и .NET/Mono.
- GIL встречается в реализациях других интерпретируемых языков. Например, стандартная имплементация [Ruby](https://www.ruby-lang.org/en/) под названием Ruby MRI содержит GIL. Только называется он Global VM Lock.
## Следствия наличия GIL
Как же правильно распараллеливать код, исполняющийся интерпретатором с GIL? Это зависит от специфики распараллеливаемых задач. {#block-cpu-bound}
**CPU-bound** задачи — это вычисления, грузящие процессор: полнотекстовый поиск, обход графа, перемножение матриц и так далее. При использовании GIL-интерпретатора получить выигрыш в производительности за счет потоков не получится. Оверхед на переключение контекста между потоками в связке с захватом и разблокировкой GIL могут сделать многопоточный код даже медленнее его однопоточной версии. Если требуется распараллелить CPU-bound работу, вместо потоков используйте процессы.
**IO-bound** задачи, такие как обращение к внешнему API, работа с бд, файлами и консольным вводом-выводом, массу времени проводят в режиме ожидания данных. А блокирующее ожидание ввода-вывода заставляет поток отпустить GIL. Поэтому распараллеливание IO-bound на потоки все же может принести выигрыш в скорости. Для достижения наилучших результатов количество потоков подбирается в зависимости от конфигурации целевой машины и специфики конкретной IO-bound задачи.
Примерно так выглядит выполнение 2-х CPU-bound потоков на машине с единственным CPU. Иллюстрация взята из блога [Дэвида Бизли](https://dabeaz.blogspot.com/2010/01/python-gil-visualized.html) — разработчика, внесшего большой вклад в развитие питона и сообщества вокруг него.
 {.illustration}
 {.illustration}
А теперь запустим 2 CPU-bound потока на машине с двумя CPU.
 {.illustration}
Иллюстрация демонстрирует, насколько неэффективно запускать несколько потоков питона в попытках распараллелить CPU-bound задачи. В интервалы времени, окрашенные красным, ОС передавала управление потоку, который пытался захватить GIL и ждал, пока его освободит другой поток.
Подытожим: для распараллеливания CPU-bound задач подойдет мультипроцессность, а для IO-bound задач — многопоточность.
## GIL и будущее питона
На данный момент развиваются два глобальных направления разработки CPython. И оба затрагивают GIL.
Полноценная поддержка **subinterpreters.** О чем речь? Начиная с версии 1.5 (то есть с 1997 года) разработчики на питоне могли воспользоваться C-API для запуска нескольких экземпляров интерпретатора в рамках одного процесса. Эти интерпретаторы разделяли состояние: таблицу имен в глобальной области видимости, кэш импортированных модулей и т.д. Соответственно GIL тоже был общим.
В версии языка 3.12 удалось добиться изоляции интерпретаторов внутри процесса: теперь у каждого из них свое состояние и свой собственный GIL. Subinterpreters по-прежнему доступны только через C-API. А полноценная поддержка планируется в версии 3.13.
Проект **nogil:** полный отказ от GIL. Исследователь-разработчик [Сэм Гросс](https://mail.python.org/archives/list/python-dev@python.org/thread/ABR2L6BENNA6UPSPKV474HCS4LWT26GY/) предложил реализацию CPython без GIL под названием [nogil.](https://github.com/colesbury/nogil) В ней удалось добиться удаления GIL таким образом, чтобы однопоточный код не потерял в производительности, а многопоточный хорошо масштабировался на ядра CPU.
Уже принят [PEP 703,](https://peps.python.org/pep-0703/) в рамках которого ведутся работы, направленные на внесение всех нужных изменений в CPython.
## Резюмируем
- GIL (global interpreter lock) — блокировка интерпретатора, которая гарантирует, что в каждый момент времени интерпретатор исполняет только один поток скрипта на питоне.
- GIL не позволяет эффективно использовать многоядерность путем распараллеливания на потоки.
- Есть два направления развития языка, связанных с GIL: отказ от GIL (nogil) и изоляция GIL для каждого интерпретатора внутри потока (subinterpreters).
- Для ускорения выполнения IO-bound задач можно использовать пул потоков. Для ускорения CPU-bound лучше подойдет пул процессов.
Наша группа в telegram. Здесь можно задавать вопросы и общаться.
Задонатить. Если вам нравится курс, вы можете поддержать развитие площадки!