Ответ
Основным и идиоматичным способом является использование sync.WaitGroup
.
1. Использование sync.WaitGroup
WaitGroup
— это счетчик, который блокирует выполнение до тех пор, пока его внутреннее значение не станет равным нулю.
Как это работает:
- Перед запуском каждой горутины вызывается
wg.Add(1)
, чтобы увеличить счетчик. - Внутри горутины с помощью
defer wg.Done()
мы гарантируем, что счетчик уменьшится при выходе из функции. - В основной горутине
wg.Wait()
блокирует выполнение, пока счетчик не обнулится.
package main
import (
"fmt"
"sync"
"time"
)
func worker(id int, wg *sync.WaitGroup) {
// Уменьшаем счетчик, когда горутина завершается
defer wg.Done()
fmt.Printf("Worker %d startingn", id)
time.Sleep(time.Second) // Симуляция работы
fmt.Printf("Worker %d donen", id)
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 3; i++ {
// Увеличиваем счетчик перед запуском горутины
wg.Add(1)
go worker(i, &wg)
}
// Ожидаем, пока все горутины не вызовут Done()
wg.Wait()
fmt.Println("All workers have finished.")
}
Важные моменты:
wg.Add()
всегда вызывается до запуска горутины, чтобы избежать состояния гонки.WaitGroup
следует передавать в функции по указателю (*sync.WaitGroup
).
2. Альтернативные подходы
-
Каналы: Можно использовать канал для сигнализации о завершении. Каждая горутина по завершении отправляет значение в канал. Основная горутина читает из канала столько раз, сколько горутин было запущено.
done := make(chan struct{}, 3) // Буферизованный канал for i := 0; i < 3; i++ { go func() { // ... работа ... done <- struct{}{} }() } for i := 0; i < 3; i++ { <-done }
-
Пакет
errgroup
: Это более продвинутый способ, особенно полезный, когда нужно обрабатывать ошибки из горутин и управлять контекстом (например, для отмены всех горутин при первой ошибке).import "golang.org/x/sync/errgroup" g, ctx := errgroup.WithContext(context.Background()) for i := 0; i < 3; i++ { g.Go(func() error { // ... работа ... // return err // При ошибке все остальные горутины будут отменены return nil }) } if err := g.Wait(); err != nil { // Обработка первой возникшей ошибки }