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

Что такое WaitGroup и как его использовать?

1.0 Junior🔥 252 комментариев
#Конкурентность и горутины

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

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

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

Что такое WaitGroup в Go?

WaitGroup — это тип из пакета sync, который предоставляет механизм синхронизации для ожидания завершения группы горутин. Это один из фундаментальных примитивов для координации параллельного выполнения в Go, особенно полезный в сценариях, где главная горутина должна дождаться завершения всех рабочих горутин перед продолжением работы.

Основная идея и принцип работы

WaitGroup работает по принципу счетчика:

  • Счетчик отслеживает количество активных горутин, которые необходимо дождаться.
  • Когда создается новая горутина, счетчик увеличивается с помощью метода Add().
  • Когда горутина завершает свою работу, счетчик уменьшается с помощью метода Done().
  • Главная горутина вызывает метод Wait(), который блокирует выполнение до тех пор, пока счетчик не достигнет нуля.

Методы WaitGroup

Тип sync.WaitGroup имеет три основных метода:

  1. Add(delta int) — увеличивает счетчик на значение delta (может быть отрицательным, но обычно положительное). Вызывается до запуска горутины.
  2. Done() — уменьшает счетчик на 1. Вызывается внутри горутины при её завершении (обычно через defer).
  3. Wait() — блокирует выполнение текущей горутины до тех пор, пока счетчик не станет равным нулю.

Базовый пример использования

Рассмотрим классический пример, где мы запускаем несколько горутин для выполнения задач и ждём их завершения:

package main

import (
    "fmt"
    "sync"
    "time"
)

func worker(id int, wg *sync.WaitGroup) {
    defer wg.Done() // Гарантируем уменьшение счетчика при любом выходе из функции
    
    fmt.Printf("Воркер %d начал работу\n", id)
    time.Sleep(time.Second * time.Duration(id)) // Имитация работы
    fmt.Printf("Воркер %d завершил работу\n", id)
}

func main() {
    var wg sync.WaitGroup
    numWorkers := 5
    
    for i := 1; i <= numWorkers; i++ {
        wg.Add(1) // Увеличиваем счетчик перед запуском каждой горутины
        go worker(i, &wg)
    }
    
    fmt.Println("Основная горутина: жду завершения всех воркеров...")
    wg.Wait() // Блокируемся до обнуления счетчика
    fmt.Println("Все воркеры завершили работу. Программа завершается.")
}

Важные практические аспекты

1. Вызов Add() до запуска горутины

Всегда вызывайте Add() в той же горутине, где будет вызван Wait(), и до запуска новой горутины. Это предотвращает состояние гонки, когда горутина может вызвать Done() раньше, чем будет вызван Add().

// ПРАВИЛЬНО:
wg.Add(1)
go func() {
    defer wg.Done()
    // работа
}()

// РИСКОВАННО (может привести к панике):
go func() {
    wg.Add(1) // Вызов в другой горутине
    defer wg.Done()
    // работа
}()

2. Использование defer для Done()

Всегда используйте defer wg.Done() в начале функции горутины. Это гарантирует, что счетчик уменьшится даже при аварийном завершении (panic).

func workerSafe(wg *sync.WaitGroup) {
    defer wg.Done() // Счетчик уменьшится при любом выходе
    // ... потенциально опасный код ...
}

3. Передача по указателю

WaitGroup должен передаваться по указателю, если используется в нескольких горутинах, чтобы все они работали с одной структурой данных.

4. Нельзя обнулять или копировать

WaitGroup нельзя копировать после первого использования. Следующий код приведет к ошибке:

var wg1 sync.WaitGroup
wg1.Add(1)
wg2 := wg1 // КОПИРОВАНИЕ ЗАПРЕЩЕНО!
go func() {
    defer wg2.Done() // Неправильно!
}()

Распространенные паттерны использования

  1. Параллельная обработка данных: обработка элементов слайса или канала несколькими горутинами
  2. Инициализация сервисов: ожидание готовности всех компонентов при запуске приложения
  3. Тестирование параллельного кода: синхронизация горутин в тестах
  4. Graceful shutdown: ожидание завершения обработки текущих запросов перед остановкой сервера

Альтернативы WaitGroup

Хотя WaitGroup идеален для простых сценариев ожидания, в более сложных случаях могут потребоваться:

  • Каналы (channels) для более сложной координации и передачи данных
  • Context для отмены операций и установки таймаутов
  • ErrGroup (пакет golang.org/x/sync/errgroup) для ожидания с обработкой ошибок

Типичные ошибки

  1. Вызов Add() внутри горутины — может привести к тому, что Wait() завершится раньше времени
  2. Паника при отрицательном счетчике — если вызвать Done() больше раз, чем Add()
  3. Deadlock — если забыть вызвать Done() или вызвать Wait() без соответствующих вызовов Add()

WaitGroup — это простой, но мощный инструмент, который является неотъемлемой частью идиоматического Go. Его правильное использование критически важно для написания корректного конкурентного кода.

🐱
qwen3-235b-a22bPrepBro AI4 апр. 2026 г.(ред.)

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

WaitGroup в Go: назначение и применение

WaitGroup — это структура из пакета sync, предназначенная для синхронизации завершения выполнения горутин. Она позволяет основной горутине ожидать завершения всех дочерних задач. Типичный сценарий: запуск нескольких независимых операций параллельно и продолжение работы после их завершения.

Основные методы WaitGroup:

  1. Add(delta int) — увеличивает внутренний счётчик на delta. Обычно вызывается перед запуском новой горутины.
  2. Done() — уменьшает счётчик на 1. Вызывается внутри горутины после выполнения задачи.
  3. Wait() — блокирует выполнение, пока счётчик не станет равным 0.

Пример использования

Предположим, нужно одновременно обработать несколько файлов:

package main

import (
	"fmt"
	"sync"
	"time"
)

func processFile(id int, wg *sync.WaitGroup) {
	defer wg.Done() // Автоматически уменьшает счётчик при выходе из функции
	fmt.Printf("Файл %d обрабатывается\n", id)
	time.Sleep(time.Second) // Имитация длительной операции
	fmt.Printf("Файл %d обработан\n", id)
}

func main() {
	var wg sync.WaitGroup
	for i := 1; i <= 3; i++ {
		wg.Add(1) // Увеличиваем счётчик на 1 перед запуском горутины
		go processFile(i, &wg)
	}
	wg.Wait() // Основная горутина ждёт завершения всех задач
	fmt.Println("Все файлы обработаны")
}

Особенности и рекомендации

  • Используйте указатель на WaitGroup: При передаче в горутину важно передавать указатель (&wg), иначе копия счётчика не будет синхронизироваться.
  • Вызывайте Add до go: Счётчик должен быть увеличен до запуска горутины, чтобы избежать гонки данных.
  • Избегайте отрицательного счётчика: Повторный вызов Done() при счётчике 0 приведёт к панике.
  • Переиспользуйте WaitGroup: После Wait() счётчик остаётся нулём, и структуру можно использовать повторно, вызвав Add заново.

Распространённые ошибки

  • Несбалансированный счётчик: Если Add и Done вызваны некорректно, программа может зависнуть или вызвать панику.
  • Wait в нескольких горутинах: Множественные вызовы Wait() на одной структуре могут привести к неопределённому поведению.

WaitGroup — легковесный и эффективный инструмент для орхестровки завершения задач. Для более сложных сценариев (например, отмена задач) стоит рассмотреть пакет context.