Goroutine легко запускать, но из-за этого легко получить одну из самых неприятных ошибок в конкурентном коде — race condition.
Race condition (гонка данных) возникает, когда результат программы зависит от того, в каком порядке выполнились goroutine.
Проще говоря: две goroutine одновременно трогают одни и те же данные
Если обе только читают — обычно проблемы нет.
Проблема начинается, когда хотя бы одна goroutine пишет.
Например:
- одна goroutine читает переменную, другая в этот момент её меняет
- две goroutine одновременно увеличивают счётчик
- несколько goroutine пишут в одну map
- несколько goroutine делают append в один slice
- одна goroutine меняет структуру, а другая одновременно читает её поля
На первый взгляд код может выглядеть нормально. Он даже может тысячу раз отработать без ошибки. А потом внезапно начнёт давать странный результат.
В этом и проблема race condition: ошибка зависит от порядка выполнения, а порядок выполнения goroutine не гарантирован.
Теперь представь две goroutine.
counter сейчас равен 0.
Первая goroutine читает 0.
Вторая goroutine тоже читает 0.
Первая увеличивает до 1 и записывает.
Вторая тоже увеличивает своё старое значение до 1 и записывает.
В итоге две goroutine выполнили работу, а в счётчике получилось 1, хотя ожидалось 2.
Схема:
Одна запись потерялась — это и есть race condition.
Race condition часто не проявляется сразу. Сегодня программа выводит правильный результат. Завтра на другой машине — неправильный. Под нагрузкой — падает. В тестах — иногда зелёная, иногда красная.
Причина в том, что scheduler Go сам решает, когда какую goroutine выполнять.
Scheduler (планировщик) — это часть runtime Go, которая распределяет goroutine по потокам ОС.
Он постоянно решает:
- какую goroutine сейчас запускать
- какую остановить
- какую разбудить после ожидания
- какую перенести на другой поток
Из-за этого порядок выполнения goroutine заранее не гарантирован, ты не контролируешь точный порядок выполнения.
Поэтому нельзя писать код в надежде, что эта goroutine точно успеет первой. В конкурентном коде такая логика ломается.