Ответ
Для синхронизации и ожидания завершения горутин в Go используются несколько основных подходов.
-
sync.WaitGroup
— основной и идиоматичный способWaitGroup
— это счетчик активных горутин. Он идеально подходит, когда нужно запустить несколько независимых горутин и дождаться, пока все они завершат свою работу.var wg sync.WaitGroup for i := 0; i < 5; i++ { wg.Add(1) // Увеличиваем счетчик перед запуском горутины go func(workerID int) { defer wg.Done() // Уменьшаем счетчик при завершении fmt.Printf("Воркер %d завершил работуn", workerID) }(i) } wg.Wait() // Блокирует выполнение, пока счетчик не станет равен нулю fmt.Println("Все воркеры завершили работу")
-
Каналы для сигнализации Каналы можно использовать для сигнализации о завершении, но для группы горутин это менее удобно, чем
WaitGroup
. Этот способ хорошо подходит для координации между двумя горутинами или когда нужно передать результат.done := make(chan bool) go func() { // ... какая-то работа ... done <- true // Отправляем сигнал о завершении }() <-done // Ожидаем сигнала
-
context
для отмены операций Контекст используется не для ожидания, а для сигнализации об отмене. Горутины должны слушать каналctx.Done()
и прекращать работу, когда он закрывается. Чтобы дождаться фактического завершения после отмены,context
часто используют вместе сWaitGroup
.var wg sync.WaitGroup ctx, cancel := context.WithCancel(context.Background()) wg.Add(1) go worker(ctx, &wg) // ... спустя какое-то время решаем остановить воркер cancel() // Отправляем сигнал отмены wg.Wait() // Ждем, пока горутина корректно завершится
-
errgroup
для групп горутин с обработкой ошибок Пакетgolang.org/x/sync/errgroup
предоставляет удобную абстракцию надWaitGroup
для случаев, когда нужно дождаться завершения группы горутин и обработать первую возникшую ошибку.g, ctx := errgroup.WithContext(context.Background()) for _, url := range urls { url := url // https://golang.org/doc/faq#closures_and_goroutines g.Go(func() error { // Эта горутина будет запущена в группе resp, err := http.Get(url) if err != nil { return err // Возвращаем ошибку } resp.Body.Close() return nil }) } // Ждем завершения всех горутин. Если одна из них вернет ошибку, // err будет не nil, и контекст g будет отменен для остальных. if err := g.Wait(); err != nil { log.Printf("Произошла ошибка: %v", err) }
Вывод: Для простого ожидания используйте sync.WaitGroup
. Для отмены — context
+ WaitGroup
. Для обработки ошибок в группе — errgroup
.