Нужны ли блокировки в многопоточном Python-коде, если есть GIL?
- Потокобезопасна ли операция x += 1 в Python?
- Какие операции в Python атомарны?
Нужны: GIL защищает внутренние структуры интерпретатора, а не ваши инварианты. x += 1 — несколько инструкций байткода (прочитать, сложить, записать), и поток может быть прерван между ними — обновления теряются. Общие изменяемые данные защищают threading.Lock или передают через queue.Queue.
- Отдельные операции над list и dict (append, d[k] = v) атомарны в CPython — но это деталь реализации
- Проверить-и-изменить (if k not in d: d[k] = ...) — гонка
- В free-threaded сборке (3.13+) гонок станет больше, а не меньше
А ещё Лучший способ — не делить изменяемое состояние: очереди и неизменяемые данные
Подробно
Что на самом деле делает GIL. В каждый момент байткод выполняет один поток, но переключение возможно между любыми инструкциями (по умолчанию каждые 5 мс). counter += 1 компилируется примерно в LOAD, BINARY_OP +=, STORE — два потока читают 5, оба пишут 6.
import threading
counter = 0
lock = threading.Lock()
def inc():
global counter
for _ in range(100_000):
with lock:
counter += 1
Без with lock итог при нескольких потоках может быть меньше ожидаемого.
Атомарность в CPython. Одиночные операции, реализованные одной инструкцией на C (list.append, dict[key] = value, deque.popleft), атомарны благодаря GIL. Но полагаться на это хрупко: сочетание двух атомарных операций уже не атомарно, а в сборке без GIL гарантии обеспечиваются по-другому.
Инструменты: Lock, RLock (реентерабельный), Condition, Semaphore, Event; queue.Queue — потокобезопасная очередь, лучший способ передавать работу между потоками.
Что хотят услышать
- GIL не защищает пользовательские данные.
- Почему x += 1 не атомарен.
- Lock и Queue.
Частые ошибки
- «Из-за GIL гонок в Python не бывает».
Проверьте себя
Один верный вариант. Подумайте, потом откройте ответ.
Показать варианты и ответ
- ✗Не нужны: x += 1 и другие простые операции атомарны в CPython, их защищает сам GIL
- ✓Нужны: GIL защищает интерпретатор, а не ваши инварианты
- ✗Блокировки нужны только в multiprocessing, где у процессов общая память и общие объекты
- ✗GIL делает любой код потокобезопасным, поэтому блокировки в потоках не нужны вовсе
Разбор темы
- Потоки, процессы и GIL
Что такое GIL и что он не защищает, когда выбирать потоки, процессы или asyncio, concurrent.futures, ограничения multiprocessing, сборка без GIL.
Связанные вопросы
Разбор собеседования с sotto
После собеседования sotto разбирает разговор: оценка, каждый вопрос — хорошо, частично или слабо, чего не хватило в ответе и над чем поработать к следующему разу. Первые 30 минут бесплатно.