Работал ли ты с wait groups для синхронизации в Go
Что проверяют
Умеете ли вы дожидаться горутин, не устраивая гонок и вечных блокировок. Вопрос простой на вид, но по ответу видно, писали ли вы конкурентный код сами: почти все грабли WaitGroup вылезают только на практике.
Как отвечать
sync.WaitGroup это счётчик запущенных задач: Add увеличивает его, Done уменьшает, Wait блокирует вызывающего, пока счётчик не обнулится.
var wg sync.WaitGroup
for _, task := range tasks {
wg.Add(1)
go func(t Task) {
defer wg.Done()
process(t)
}(task)
}
wg.Wait()Три правила, которые и хотят услышать: Add вызывается до запуска горутины, Done ставится через defer первой строкой, сама группа передаётся указателем.
Дальше покажите границы инструмента. Группа не собирает ошибки и не ограничивает параллелизм. Для ошибок берут errgroup: он возвращает первую ошибку, а вместе с контекстом отменяет остальные задачи. Для ограничения числа одновременных задач нужен семафор на буферизованном канале. В свежих версиях Go у группы появился метод Go, который сам делает Add и Done, и это уже устраняет половину ошибок.
Где спотыкаются
Вызывают Add внутри горутины. Планировщик не обязан запускать её немедленно, поэтому Wait видит нулевой счётчик и проходит мимо, часть результатов теряется. Воспроизводится через раз и потому долго живёт в проде.
Передают группу по значению. Копия считает свой счётчик, исходный не обнуляется никогда, и программа встаёт на Wait. Такое копирование ловит штатный статический анализатор, но только если его запускают.
Теряют Done на раннем выходе по ошибке. Без defer любая ветка с return оставляет счётчик ненулевым, а у Wait нет таймаута, так что сервис просто перестаёт отвечать, и в логах нет ни одной строчки про причину.