Состояние гонки (race condition)
Самый коварный класс багов: результат зависит от того, в каком порядке выполнились потоки.
Состояние гонки (race condition) — это ситуация, когда корректность программы зависит от относительного порядка или времени выполнения нескольких потоков, а этот порядок не гарантирован.
Гонки возникают там, где несколько потоков читают и пишут одни и те же данные без согласования. Классический пример — общий счётчик, который инкрементируют два потока.
Почему x += 1 не атомарна
Кажется, что counter += 1 — одна операция. На самом деле это три шага: прочитать значение, прибавить единицу, записать обратно. Между этими шагами планировщик может переключиться на другой поток.
Поток A Поток B
read counter=5
read counter=5
add -> 6
add -> 6
write counter=6
write counter=6
Два инкремента, а итог 6, а не 7. Один потерян!Это и есть гонка: оба потока прочитали 5, оба записали 6, и одно увеличение «потерялось». Результат зависит от точного момента переключения — поэтому баг плавающий и трудно воспроизводимый.
Демонстрация «потерянного обновления»
Смоделируем перемежающиеся шаги вручную, без настоящих потоков, чтобы показать, как теряется обновление:
counter = 5
# оба потока прочитали ДО записи
a_read = counter
b_read = counter
# оба прибавили на своей копии
a_new = a_read + 1
b_new = b_read + 1
# оба записали
counter = a_new
counter = b_new
print("Ожидали 7, получили:", counter)Вывод:
Ожидали 7, получили: 6
Как работает под капотом
Процессор и компилятор могут переупорядочивать инструкции и кэшировать значения в регистрах. Даже простое чтение-изменение-запись разбивается на несколько машинных команд, и вытесняющий планировщик способен вклиниться между ними. Чем больше потоков и общих данных, тем выше шанс попасть в «неудачное» чередование. Гонки особенно опасны тем, что в тестах могут не проявляться, а в продакшене под нагрузкой — внезапно сломать данные.
Частые ошибки
- Считать любую короткую операцию атомарной.
+=,appendв общий список, проверка-и-действие — всё это уязвимо. - «У меня всё работает». Гонка может проявляться раз в тысячу запусков; отсутствие падения не доказывает корректность.
- Защищать только запись, забывая про чтение. Несогласованное чтение тоже даёт мусор.
Итог
- Гонка — зависимость результата от порядка выполнения потоков.
x += 1— это read-modify-write, не одна атомарная операция.- Баги гонок плавающие и плохо воспроизводимые.
- Любой общий изменяемый доступ нужно согласовывать.