Как используется WaitGroup для ожидания завершения горутин в Go
Что проверяют
Знаете ли вы правила, при нарушении которых счётчик уходит в отрицательное значение или программа виснет.
Как отвечать
sync.WaitGroup это счётчик незавершённых задач: Add увеличивает, Done уменьшает, Wait блокирует до нуля.
var wg sync.WaitGroup
for _, url := range urls {
wg.Add(1) // до запуска горутины
go func() {
defer wg.Done() // сработает даже при панике
fetch(url)
}()
}
wg.Wait()Правила, которые и проверяют:
Addвызывается доgo, а не внутри горутины. ИначеWaitможет выполниться раньше, чем счётчик увеличится, и программа продолжит работу с недоделанными задачами;Doneчерезdefer, чтобы паника или ранний возврат не подвесили ожидание;- группа не копируется: передавать нужно указатель, иначе горутина уменьшит счётчик у своей копии. Это ловит
go vet; - счётчик не уходит в минус: лишний
Doneвызывает панику; - повторное использование возможно, но только после того, как предыдущий
Waitзавершился; - закрытие канала результатов обычно выносят в отдельную горутину:
go func() { wg.Wait(); close(results) }().
Полезно назвать более удобную замену для типичного случая: errgroup.Group умеет то же самое, но собирает первую ошибку, отменяет остальные задачи через контекст и ограничивает параллелизм методом SetLimit. В новом коде чаще берут именно его.
С версии 1.22 переменная цикла своя на каждой итерации, поэтому классическая ошибка с захватом общей переменной больше не воспроизводится.
Где спотыкаются
Вызывают Add внутри горутины и получают гонку с Wait.
Передают группу по значению в функцию.
Забывают Done в ветке с ранним возвратом, и программа виснет навсегда.