Комментарии (2)
Ответ сгенерирован нейросетью и может содержать ошибки
Основное назначение sync.WaitGroup.Wait()
sync.WaitGroup.Wait() — это метод синхронизации в Go, который блокирует выполнение текущей горутины до тех пор, пока внутренний счетчик WaitGroup не достигнет нуля. Это фундаментальный инструмент для оркестрации работы нескольких горутин и ожидания их завершения перед продолжением выполнения основной программы или следующего этапа вычислений.
Ключевая механика работы
WaitGroup работает по принципу счетчика с тремя основными методами:
- Add(delta int) — увеличивает счетчик на
delta(обычно положительное число, например, количество запускаемых горутин). - Done() — уменьшает счетчик на 1 (вызывается внутри завершающейся горутины, часто через
defer). - Wait() — блокирует выполнение, пока счетчик не станет равным 0.
package main
import (
"fmt"
"sync"
"time"
)
func main() {
var wg sync.WaitGroup
for i := 1; i <= 3; i++ {
wg.Add(1) // Увеличиваем счетчик перед запуском каждой горутины
go worker(i, &wg)
}
fmt.Println("Главная горутина: запустила воркеров и ждёт...")
wg.Wait() // Блокировка здесь до завершения всех worker()
fmt.Println("Главная горутина: все воркеры завершились, программа завершает работу.")
}
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done() // Уменьшаем счетчик при выходе из функции (гарантированно)
fmt.Printf("Воркер %d: начал работу\n", id)
time.Sleep(time.Duration(id) * time.Second) // Имитация работы
fmt.Printf("Воркер %d: завершил работу\n", id)
}
Типичные сценарии использования
-
Ожидание завершения группы параллельных задач
// Параллельная обработка элементов слайса results := make([]string, len(items)) var wg sync.WaitGroup for i, item := range items { wg.Add(1) go func(idx int, data Item) { defer wg.Done() results[idx] = process(data) // Конкурентная обработка }(i, item) } wg.Wait() // Ждем обработки всех элементов // Теперь results полностью заполнен -
Оркестрация этапов конвейерной обработки (pipeline)
// Этап 1: читаем данные stage1 := make(chan Data) go producer(stage1) // Этап 2: обрабатываем данные несколькими воркерами stage2 := make(chan Result) var wg sync.WaitGroup for i := 0; i < workerCount; i++ { wg.Add(1) go func() { defer wg.Done() for data := range stage1 { stage2 <- process(data) } }() } // Отдельная горутина для закрытия stage2 после завершения всех воркеров go func() { wg.Wait() // Ждем завершения всех воркеров close(stage2) // Закрываем канал выхода }() -
Ограничение времени выполнения группы операций
var wg sync.WaitGroup done := make(chan struct{}) for _, task := range tasks { wg.Add(1) go func(t Task) { defer wg.Done() t.Execute() }(task) } // Горутина, которая сигнализирует о завершении всех задач go func() { wg.Wait() close(done) }() // Ожидаем либо завершения всех задач, либо таймаута select { case <-done: fmt.Println("Все задачи завершены успешно") case <-time.After(5 * time.Second): fmt.Println("Таймаут: не все задачи завершились за отведенное время") }
Важные аспекты и лучшие практики
- Передача по указателю:
WaitGroupдолжен передаваться в функции по указателю, так как содержит внутреннее состояние, которое должно изменяться. - Правильный порядок вызовов:
Add()следует вызывать до запуска горутины, а не внутри неё, чтобы избежать гонок. - Гарантированное уменьшение счетчика: Используйте
defer wg.Done()для гарантированного уменьшения счетчика даже при панике. - Отрицательный счетчик: Вызов
Done()больше раз, чемAdd(), приведет к панике с отрицательным счетчиком. - Повторное использование: После того как
Wait()вернул управление,WaitGroupможно использовать снова, но нельзя начинать новое ожидание, пока предыдущее еще не завершилось.
Сравнение с альтернативами
- Каналы: Можно использовать каналы для синхронизации, но
WaitGroupболее идиоматичен и эффективен для сценариев "ожидания N операций". - errgroup.Group: Пакет
golang.org/x/sync/errgroupрасширяетWaitGroup, добавляя обработку ошибок и контекст для отмены. - sync.Once: Для однократного выполнения, а не ожидания группы операций.
Заключение: sync.WaitGroup.Wait() — это краеугольный камень конкурентного программирования на Go, обеспечивающий простой и эффективный способ синхронизации горутин. Его правильное использование позволяет создавать надежные параллельные программы, которые корректно завершаются и эффективно используют ресурсы многопроцессорных систем.