Ошибки WaitGroup
Первый запуск со счётчиком дал странный симптом: программа завершалась, не дождавшись задач пакета. Миша нашёл баг — счётчик ожидания увеличивали уже после старта задачи, внутри неё самой. Он перенёс регистрацию до запуска, и отчёт снова дожидался всех.
у WaitGroup три классические ошибки: Add внутри горутины (гонка), копирование структуры (вешает Wait) и отрицательный счётчик (паника).
Add внутри горутины
var wg sync.WaitGroup
for _, f := range files {
go func(f File) {
wg.Add(1) // баг: Add после запуска
defer wg.Done()
process(f)
}(f)
}
wg.Wait()
Wait может вернуться до того, как горутины сделают Add: счётчик ещё ноль, ждать нечего. Add обязан стоять до go — это условие, а не стиль.
Копирование
var wg sync.WaitGroup
wg.Add(1)
wg2 := wg // копия внутреннего состояния
go func() { wg2.Done() }()
wg.Wait() // deadlock: оригинальная wg никогда не узнает о Done
WaitGroup копировать нельзя. Done по копии уменьшает счётчик копии, а Wait ждёт оригинал — навсегда. Правило: передавай WaitGroup только по указателю, идиоматично — в замыкании горутины использовать ту же переменную.
Отрицательный счётчик
var wg sync.WaitGroup
wg.Done() // panic: negative WaitGroup counter
Done без Add уводит счётчик в минус — паника. Обычно это симптом парного бага: либо Add потерялся, либо Done вызван дважды (например, Done и в defer, и в теле).
Вы узнаете:
- как race detector ловит
Addв горутине - как
go vetловит копии блокировок
Проверьте себя
var wg sync.WaitGroup; wg.Done() без Add. Что произойдёт?
Почему нельзя копировать WaitGroup?
Как защититься от двойного Done?
правильных ответов
Add — до старта и вне горутины; копий не делать; Done — ровно по одному на Add. Три правила, три симптома: гонка, дедлок, паника.
10 000 ₽ · 60 минут · онлайн