Есть у конкурентности особый сорт багов, после которых хочется закрыть ноутбук и пойти погулять. Тесты зелёные, локально всё сходится. А потом приходит отчёт с прода, и сумма в нём меньше, чем должна быть. Не вдвое, а вот на чуть-чуть, будто пара операций куда-то потерялась по дороге.
Они и правда потерялись.
Дело в том, что
counter++ только выглядит как одно действие. На самом деле это прочитать, прибавить, записать обратно. И если между «прочитать» и «записать» в ту же переменную успела влезть соседняя горутина, её работа затирается. Без ошибки и без паники.Обиднее всего, что повторить это специально почти нереально. Запускаешь двадцать раз, двадцать раз всё хорошо. Поэтому такие штуки спокойно живут в проекте месяцами и вылезают в самый неподходящий момент.
Но ловить их Go умеет сам:
go test -raceОн расставляет проверки и прямо показывает, какие горутины полезли в одну память и в каких строчках. Первый запуск
-race на своём проекте — обычно смешанные ощущения, потому что почти всегда там находится что-то, о чём ты не подозревал.