Почему в Python потоки не выполняются параллельно из-за GIL и как это влияет на производительность
Что проверяют
Понимаете ли вы, что GIL это деталь реализации CPython, а не свойство языка, и умеете ли подбирать инструмент под тип нагрузки. Уточняющий вопрос почти всегда один: как ускорить счётную задачу и что изменилось со свободнопоточными сборками.
Как отвечать
GIL это глобальная блокировка интерпретатора: байткод исполняет только тот поток, который ей владеет. Причина практическая, подсчёт ссылок у объектов не атомарен, и одна общая блокировка делает его безопасным дёшево.
- счётная нагрузка: потоки не ускоряют, а часто замедляют. Владение передаётся по таймеру
sys.setswitchinterval(по умолчанию 5 мс), плюс накладные расходы на борьбу за блокировку; - нагрузка на ввод-вывод: GIL отпускается на время системного вызова, поэтому сотня потоков, ждущих сеть или диск, работает нормально;
- обход:
multiprocessingиProcessPoolExecutorдают отдельный интерпретатор на процесс, плата это сериализация данных; расширения на C и Rust, numpy и polars отпускают GIL внутри вычислений;asyncioдаёт конкурентный ввод-вывод в одном потоке.
Дальше современная часть, ради которой вопрос и живёт. PEP 703 добавил сборку без GIL: в Python 3.13 она была экспериментальной, а в 3.14 свободнопоточный режим переведён в официально поддерживаемый. Важные оговорки: это по-прежнему отдельная сборка, а не поведение по умолчанию; одиночный поток в ней немного медленнее; C-расширения обязаны заявить совместимость через слот Py_mod_gil, иначе интерпретатор включит GIL обратно. Рядом стоит PEP 684: с 3.12 у субинтерпретаторов свой GIL, а в 3.14 появился штатный модуль concurrent.interpreters.
Где спотыкаются
Говорят «в Python есть GIL». GIL есть в CPython и PyPy, а Jython, IronPython и GraalPy обходятся без него. Формулировка выдаёт заученный ответ.
Предлагают threading для ускорения расчётов, а multiprocessing для параллельных сетевых запросов. Это ровно наоборот, и такую путаницу проверяют одним встречным вопросом.
Считают, что сборка без GIL сама по себе ускоряет код. Она снимает блокировку, но возвращает обычные гонки данных: разделяемые структуры надо защищать самому, иначе вместо прироста получите редкие плавающие ошибки.