Что произойдет, если будет разное количество горутин и wg.Add?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Работа с sync.WaitGroup и дисбалансом между горутинами и вызовами wg.Add()
Ключевой механизм sync.WaitGroup предназначен для ожидания завершения группы горутин. Его основные методы:
wg.Add(n)— увеличивает счетчик ожидаемых горутин наn.wg.Done()— уменьшает счетчик на 1 (вызывается каждой горутиной при завершении).wg.Wait()— блокирует выполнение, пока счетчик не станет равным 0.
Основные проблемы при дисбалансе
Если количество горутин и вызовов wg.Add() не совпадает, возникают следующие ситуации:
1. wg.Add() больше, чем горутин (счетчик остаётся положительным)
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
wg.Add(5) // Ожидаем 5 горутин
for i := 0; i < 3; i++ { // Запускаем только 3
go func(id int) {
defer wg.Done()
fmt.Printf("Горутина %d завершилась\n", id)
}(i)
}
wg.Wait() // Блокируется навсегда: счетчик остался 2
fmt.Println("Основная горутина завершится? Нет!")
}
Результат: wg.Wait() будет ждать бесконечно, программа зависнет (или завершится по таймауту в контексте тестов).
2. wg.Add() меньше, чем горутин (счетчик становится отрицательным)
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
wg.Add(2) // Ожидаем 2 горутин
for i := 0; i < 5; i++ { // Запускаем 5
go func(id int) {
defer wg.Done()
fmt.Printf("Горутина %d завершилась\n", id)
}(i)
}
wg.Wait() // Счетчик уйдёт в отрицательные значения
fmt.Println("Основная горутина завершилась, но...")
}
Результат: Вызов wg.Done() больше, чем ожидалось, приводит к панике:
panic: sync: negative WaitGroup counter
Это происходит потому, что внутренний счетчик WaitGroup становится меньше нуля, что считается недопустимым состоянием.
Почему это критично и как избежать проблем
WaitGroup не является "счётчиком горутин" в общем смысле — он отслеживает конкретные задачи, которые должны завершиться. Несоответствие нарушает его контракт.
Правильные паттерны использования:
- Вызов
wg.Add()непосредственно перед запуском горутины (в той же goroutine, чтобы избежать race conditions):
var wg sync.WaitGroup
for i := 0; i < n; i++ {
wg.Add(1) // Увеличиваем на 1 для каждой новой горутины
go func(id int) {
defer wg.Done()
// Работа горутины
}(i)
}
wg.Wait()
- Атомарный вызов
wg.Add()с общим числом, если известно точное количество заранее:
wg.Add(n) // n должно точно соответствовать количеству запускаемых горутин
for i := 0; i < n; i++ {
go func(id int) {
defer wg.Done()
// Работа горутины
}(i)
}
- Использование цикла или очереди задач, где количество элементов соответствует вызовам
wg.Add().
Резюме:
- Несоответствие приводит либо к deadlock (
Wait()бесконечно), либо к панике (negative counter). - WaitGroup требует точного соответствия: каждый запуск горутины, которую нужно дождаться, должен быть учтен через
wg.Add(), и каждая такая горутина обязана вызватьwg.Done(). - Рекомендуется вызывать
wg.Add()как можно ближе к запуску горутины, часто внутри того же потока выполнения, чтобы не допустить ситуации, где горутина уже вызвалаDone(), аAdd()ещё не был выполнен.
Нарушение этого баланса — одна из частых ошибок при работе с многозадачностью в Go, которая может привести к нестабильности программы.