Что такое состояние гонки — баг, который появляется только иногда

Самый бесящий баг новичка — тот, что случается через раз. Нажимаешь кнопку — работает. Нажимаешь ещё — сломалось. Пробуешь показать другу — снова работает. Начинаешь его ловить пошагово — он исчезает. За такими багами часто стоит одна и та же штука: состояние гонки.
Название честное: две операции реально гонятся за один и тот же ресурс, и кто прибежит первым — заранее не известно. Иногда порядок случайно правильный, и всё хорошо. Иногда нет — и вылезает баг. Через пару минут ты будешь понимать, откуда берётся эта «то есть, то нет».
Что такое состояние гонки простыми словами
Состояние гонки (race condition) — это когда результат зависит от порядка, в котором сработали два действия, а порядок этот не гарантирован. Программа думала, что шаги пойдут один за другим. А они пошли внахлёст.
Так бывает, потому что современный код часто не ждёт: пока одна операция тянется (запрос к серверу, чтение из базы), программа берётся за следующую. Обычно это благо — интерфейс не замирает. Но если две такие операции трогают одну и ту же вещь, они могут наступить друг другу на пятки.
Аналогия: вы вдвоём редактируете один бумажный список. Ты берёшь листок, дописываешь строку, кладёшь обратно. Друг в это же время взял старую копию, дописал своё, положил поверх твоей. Твоей строки больше нет — её затёрли. Никто не ошибся. Просто вы работали с общим листком без очереди.
Почему баг «то есть, то нет»
Разберём на классике — счётчик. Два запроса одновременно хотят увеличить число на 1. Внутри каждый делает три шага:
- прочитать текущее значение (было 100);
- прибавить 1 (стало 101);
- записать обратно.
Если запросы идут по очереди, получится 102 — как надо. Но если они наложились: оба прочитали 100, оба прибавили 1, оба записали 101. Одно увеличение потерялось. Должно было стать 102, а стало 101.
Теперь понятно, почему баг неуловим. Всё зависит от микротайминга — разницы в доли миллисекунды. На твоём компьютере запросы почти всегда успевают разойтись, и баг спит. Под нагрузкой, когда пользователей много, наложения случаются постоянно — и он просыпается. Отсюда и «локально работает, на проде глючит».
Где это больно на практике: два клика по «Оплатить» — и деньги списались дважды. Два процесса пишут в один файл — получается каша. ИИ написал код, который дёргает базу из нескольких мест разом — и данные разъезжаются. Все эти баги — родственники.
Как этого избежать
Общая идея одна: не давай двум операциям трогать общий ресурс как попало. Наведи очередь. На практике это несколько приёмов:
- Сделай операцию неделимой (атомарной). Многие базы данных умеют «прибавить 1» одной командой, которую нельзя прервать посередине. Тогда шаги «прочитать — прибавить — записать» не расползаются, и гонки нет.
- Блокируй кнопку после клика. Самая частая гонка в интерфейсе. Нажали «Оплатить» — сразу гаси кнопку, пока не придёт ответ. Двойного списания не будет.
- Проверяй, не сделано ли уже. Прежде чем создать заказ, убедись, что такого ещё нет. Это спасает от повторов, даже если запрос случайно ушёл дважды.
И первый шаг к починке — воспроизвести баг. Раз он зависит от тайминга, лови его под нагрузкой: запусти действие много раз подряд или в параллель. А чтобы понять, в каком порядке всё сработало, включи подробные логи с метками времени — они покажут наложение, которое глазами не видно.
Частые вопросы
Чем состояние гонки отличается от обычного бага?
Обычный баг воспроизводится: те же действия — та же ошибка. Гонка непостоянна — она зависит от того, что за операция успела раньше, а это меняется от запуска к запуску. Поэтому её так трудно поймать: пошаговая отладка меняет тайминг, и баг прячется. За такое непостоянство их прозвали «гейзенбагами» — исчезают, когда за ними наблюдаешь.
Гонка бывает только при многопоточности?
Нет. Классически — да, когда несколько потоков работают параллельно. Но новичок встречает её чаще в асинхронном коде и в вебе: два запроса к серверу, два клика, две вкладки одного приложения. Технически это один поток, но операции всё равно накладываются во времени — и ресурс общий.
Почему это не ловится на моём компьютере?
Потому что у тебя одновременных действий мало — операции почти всегда успевают разойтись. Гонка любит нагрузку: чем больше пользователей и запросов разом, тем выше шанс наложения. Классический сценарий — «локально идеально, на проде через раз падает».
Короткие уроки-истории, симулятор агента и ежедневная практика — в нашем мобильном приложении. Бесплатно.





