Встречается на 25% собеседований по Go

Что такое WaitGroup

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

Как это работает

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

Важно: Add должен вызываться до Wait, иначе возможна гонка данных. Рекомендуется вызывать Add в главной горутине до запуска горутин, а Done — через defer внутри горутины, чтобы гарантировать уменьшение счётчика даже при панике.

Пример

go
package main

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

func worker(id int, wg *sync.WaitGroup) {
    defer wg.Done() // гарантированно уменьшаем счётчик при выходе

    fmt.Printf("Worker %d starting\n", id)
    time.Sleep(time.Second) // имитация работы
    fmt.Printf("Worker %d done\n", id)
}

func main() {
    var wg sync.WaitGroup

    numWorkers := 3
    wg.Add(numWorkers) // устанавливаем счётчик на 3

    for i := 1; i <= numWorkers; i++ {
        go worker(i, &wg) // запускаем горутины
    }

    wg.Wait() // ждём завершения всех горутин
    fmt.Println("All workers completed")
}

Здесь wg.Add(3) задаёт счётчик, каждая горутина вызывает Done() через defer, а Wait() блокирует main до тех пор, пока все три горутины не завершатся.

Подводные камни

  • Копирование WaitGroup: структуру нельзя копировать после использования, так как это приведёт к некорректной работе счётчика. Передавайте её по указателю.
  • Вызов Add внутри горутины: если Add вызывается внутри горутины, а Wait уже запущен, возможна гонка. Лучше вызывать Add до запуска горутин.
  • Отрицательный счётчик: если Done вызывается больше раз, чем Add, возникает паника. Следите за балансом.
  • Использование с defer: всегда используйте defer wg.Done() в начале горутины, чтобы избежать пропуска при раннем выходе.

Когда использовать

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

Коротко

  • WaitGroup синхронизирует горутины через счётчик: Add увеличивает, Done уменьшает, Wait блокирует до нуля.
  • Передавайте WaitGroup по указателю, не копируйте.
  • Вызывайте Add до запуска горутин, Done — через defer.
  • Используйте для ожидания завершения фиксированного числа горутин; для более сложных сценариев — каналы или errgroup.
как отвечать на вопрос
пример собеседования
фреймворки на собеседовании
типичные вопросы junior
интервью вопросы и ответы