Как работает конструкция для ожидания выполнения горутин в Go
Что проверяют
Знаете ли вы sync.WaitGroup не на уровне трёх методов, а с его правилами: где вызывать Add, почему группу нельзя копировать и что будет при ошибке в счётчике. Следом часто просят дождаться горутин и вернуть первую ошибку, то есть перейти к errgroup.
Как отвечать
WaitGroup это счётчик незавершённых задач:
Add(n): увеличивает счётчик. Вызывается в запускающей горутине доgo, а не внутри новой: иначеWaitможет проскочить раньше, чем счётчик вырос;Done(): то же, чтоAdd(-1). Ставится черезdeferпервой строкой, чтобы сработать и при раннемreturn, и при панике;Wait(): блокирует, пока счётчик не станет нулём. После возврата группу можно использовать заново;Go(f): метод из Go 1.25, который сам делаетAdd(1), запускаетfв горутине и вызываетDone. В новом коде он убирает целый класс ошибок.
Правила, которые стоит проговорить. WaitGroup передаётся по указателю или захватывается замыканием: копия ведёт свой счётчик, и дело кончается дедлоком (go vet ловит это проверкой copylocks). Уход счётчика в минус вызывает панику. Сама группа не защищает данные: если горутины пишут в общий срез или мапу, нужен мьютекс, канал или запись по своему индексу в заранее выделенный срез.
Для реальных задач упомяните golang.org/x/sync/errgroup: функция в g.Go возвращает ошибку, g.Wait() отдаёт первую из них, errgroup.WithContext отменяет контекст остальным, а SetLimit ограничивает число одновременных горутин.
Где спотыкаются
Вызывают wg.Add(1) внутри запущенной горутины. Код иногда работает, но Wait может вернуться раньше, чем хоть одна горутина успела увеличить счётчик.
Передают WaitGroup в функцию по значению. Done уменьшает счётчик копии, оригинал никогда не дойдёт до нуля, и программа виснет на Wait.
Путают ожидание с отменой. WaitGroup не умеет остановить горутины при первой ошибке или по таймауту, для этого нужен context.Context или errgroup.WithContext. Горутина, которая не слушает контекст, так и держит Wait.