Ответ
Классический и наиболее идиоматичный способ в Go — использовать комбинацию sync.WaitGroup
для ожидания завершения всех горутин и каналов для безопасной передачи результатов.
Основной паттерн: WaitGroup + Channels
sync.WaitGroup
: Используется как счетчик активных горутин. Главная горутина ждет, пока счетчик не обнулится.- Канал: Служит потокобезопасной очередью для сбора результатов от каждой горутины.
package main
import (
"fmt"
"sync"
"time"
)
// worker выполняет "работу" и отправляет результат в канал
func worker(id int, wg *sync.WaitGroup, results chan<- string) {
defer wg.Done() // Уменьшаем счетчик WaitGroup по завершении
fmt.Printf("Воркер %d начал работуn", id)
time.Sleep(time.Second) // Имитация работы
results <- fmt.Sprintf("Результат от воркера %d", id)
fmt.Printf("Воркер %d закончил работуn", id)
}
func main() {
const numWorkers = 5
var wg sync.WaitGroup
// Буферизированный канал, чтобы воркеры не блокировались при отправке результата
results := make(chan string, numWorkers)
// Запускаем воркеров
wg.Add(numWorkers)
for i := 1; i <= numWorkers; i++ {
go worker(i, &wg, results)
}
// Запускаем отдельную горутину, которая закроет канал
// после того, как все воркеры завершат свою работу.
// Это позволяет безопасно использовать range для чтения из канала.
go func() {
wg.Wait()
close(results)
}()
// Читаем все результаты из канала, пока он не будет закрыт
fmt.Println("Ожидание результатов...")
for res := range results {
fmt.Println("Получен:", res)
}
fmt.Println("Все результаты получены.")
}
Альтернатива: golang.org/x/sync/errgroup
Для сценариев, где важна обработка ошибок и контекста (например, отмена всех горутин при первой ошибке), пакет errgroup
предоставляет более высокоуровневую и удобную абстракцию. Он инкапсулирует WaitGroup
и обработку ошибок.