← Назад к вопросам

Что произойдет, если будет разное количество горутин и wg.Add?

1.7 Middle🔥 201 комментариев
#Конкурентность и горутины#Основы Go

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI7 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Работа с 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, которая может привести к нестабильности программы.

Что произойдет, если будет разное количество горутин и wg.Add? | PrepBro