Почему при использовании одного wait group в нескольких горутинах возникают проблемы и как это исправить
Что проверяют
Посылка тут тоже кривая: один sync.WaitGroup на группу горутин это нормальный и единственно осмысленный способ им пользоваться. Проверяют знание контракта: кто вызывает Add и когда, кто вызывает Done, и почему группу нельзя копировать.
Как отвечать
Add вызывается в той горутине, которая запускает работу, до go. Если написать wg.Add(1) внутри горутины, Wait имеет полное право пройти раньше, чем планировщик до неё доберётся, и программа завершится, не дождавшись ничего. Под нагрузкой это не редкая гонка, а обычное поведение.
Done ставится через defer первой строкой тела, чтобы ранний return или паника не оставили счётчик ненулевым. Иначе Wait будет ждать вечно, и это даже не определится как взаимная блокировка: процесс просто стоит. Лишний Done роняет программу сообщением про отрицательный счётчик.
Передавать только указателем или через замыкание. Значение sync.WaitGroup содержит счётчик, копия получает свой собственный, Done уменьшает копию, Wait ждёт оригинал. Ловится командой go vet.
for _, item := range items {
wg.Add(1)
go func(it Item) {
defer wg.Done()
process(it)
}(item)
}
wg.Wait()Переиспользовать одну группу можно только последовательно: новый Add после того, как предыдущий Wait уже вернулся. И стоит сказать, чего группа не умеет: она не возвращает ошибок и не отменяет работу. Когда нужна первая ошибка и остановка остальных, берут errgroup.Group с контекстом.
Где спотыкаются
Переносят Add внутрь горутины, потому что «так логичнее по месту». Тест на трёх элементах проходит, на боевой нагрузке часть работы теряется молча.
Объявляют функцию как func worker(wg sync.WaitGroup) и получают зависший Wait, который невозможно объяснить по логам. Подсказку выдаёт статический анализ, но её обычно не читают.
Считают, что WaitGroup защищает общие данные. Она задаёт только момент, когда все закончили, а конкурентная запись результатов в общий срез или map остаётся гонкой. Нужен мьютекс, канал или запись каждой горутины строго в свой индекс.