Гонка потоков в Python: почему print может вернуть False

Гонка потоков в Python: почему print может вернуть False

@python_quiz

Разберем этот квиз

Коротко о задаче

python

Варианты ответа предлагали, будет ли этот код всегда печатать True, всегда False, может печатать и то, и другое, или упадёт с исключением. Разберёмся, что реально происходит и почему.

Почему результат недетерминирован

  • Thread.start() создаёт и запускает поток асинхронно — управление возвращается сразу в вызывающий поток.
  • print(flag) может выполниться до того, как другой поток выполнит присваивание.
  • В CPython существует GIL (Global Interpreter Lock), но он не гарантирует порядок выполнения потоков и не устраняет гонки данных между потоками.
  • В данном коде нет синхронизации, поэтому возможны оба сценария: основной поток печатает False (если ещё не произошло обновление), или печатает True (если второму потоку удалось выполнить обновление раньше).

Анализ вариантов ответов

  • "Всегда выведет True" — неверно: нет синхронизации, присваивание может произойти позже.
  • "Всегда выведет False" — неверно: поток может успеть изменить переменную до print.
  • "Может вывести и True, и False" — верно в духе поведения программы: результат может быть разным в разных прогоновах и на разных системах.
  • "Код упадёт с исключением" — неверно: вызов globals().update({'flag': True}) корректен и исключений не вызывает в этом контексте.

Демонстрация: эксперимент множеством прогонов

python

На практике в серии запусков вы часто увидите оба значения в счётчике — поведение зависит от планировщика потоков, скорости выполнения и платформы.

Как исправить (гарантировать True)

  1. Использовать join(), чтобы дождаться окончания потока:
python
  1. Использовать threading.Event для явной синхронизации:
python
  1. Для более сложных случаев — Lock, Condition или другие примитивы синхронизации.

Короткие рекомендации

  • Не полагайтесь на порядок выполнения потоков без синхронизации.
  • Для обмена состоянием между потоками используйте join, Event, Lock или очереди (queue).
  • GIL не гарантирует отсутствие гонок и не даёт порядка между потоками — он лишь предотвращает одновременное исполнение байткода несколькими потоками в CPython.

Вывод
Без явной синхронизации поведение этого кода недетерминировано: в разных ситуациях он может печатать как True, так и False. Чтобы получить предсказуемое поведение, нужно дождаться завершения потока или использовать примитивы синхронизации.

Report Page