Ответ
Классический и наиболее идиоматичный способ в 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 и обработку ошибок.
Ответ 18+ 🔞
А, слушай, смотри, классический способ, который все эти умники в го используют — это взять sync.WaitGroup, чтобы горутины дождаться, и каналы, чтобы результаты собрать, не устроив при этом пиздец и срач в памяти. Безопасно, идиоматично, как учили.
Паттерн-то какой: WaitGroup + Каналы
sync.WaitGroup: Это, блядь, такой счетчик, типа "сколько горутин ещё пашут". Главная программа на него смотрит и ждёт, пока он в ноль не уйдёт, как будто ждёт, когда все мудаки с обеда вернутся.- Канал: Это уже труба, куда каждая горутина свой результат суёт. Потокобезопасно, без толкотни, всё чинно-благородно, если, конечно, не забыть её закрыть, а то вечно виснем потом, сука.
package main
import (
"fmt"
"sync"
"time"
)
// worker делает вид, что работает, и пихает результат в канал
func worker(id int, wg *sync.WaitGroup, results chan<- string) {
defer wg.Done() // Гарантированно говорим "я всё, свободен", даже если посередине пиздец случится
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)
}
// А вот тут хитрость, ёпта! Запускаем анонимную горутину-сторожа.
// Её задача — дождаться, пока ВСЕ воркеры отпишутся (wg.Wait()),
// и тогда БЛЯДЬ ЗАКРЫТЬ КАНАЛ. Это важно, а то в цикле чтения ниже зависнем навеки.
go func() {
wg.Wait()
close(results)
}()
// А тут главная программа просто читает из канала, пока он не закроется.
// Красиво, просто, ни одной лишней проверки.
fmt.Println("Сижу, жду, когда работа сделается...")
for res := range results {
fmt.Println("Прилетело:", res)
}
fmt.Println("Всё, приехали. Можно расходиться.")
}
Если хочешь поумничать: golang.org/x/sync/errgroup
А вот если тебе ещё и ошибки ловить надо, да чтобы всё по щелчку отменялось, если один мудак всё проебал — тогда есть пакетик errgroup. Он, сука, как продвинутая обёртка над WaitGroup, сам и ошибки соберёт, и контекст прикрутить можно. Для серьёзных пацанов, одним словом.