Как агрегировать ответы от нескольких горутин через каналы в Go
агрегирование данныхасинхронностьгорутиныканалымногопоточность
Что проверяют
Умеете ли вы дождаться всех горутин и не оставить ни одной висеть. Тут же обычно проверяют знание errgroup и отмены.
Как отвечать
Базовая схема: каждая горутина пишет результат в общий канал, отдельная горутина ждёт завершения всех и закрывает канал, а получатель читает через range до закрытия.
results := make(chan Result, len(urls))
var wg sync.WaitGroup
for _, url := range urls {
wg.Add(1)
go func() { // с Go 1.22 переменная цикла своя на итерацию
defer wg.Done()
results <- fetch(url)
}()
}
go func() { wg.Wait(); close(results) }() // закрывает тот, кто владеет записью
var all []Result
for r := range results {
all = append(all, r)
}Ключевые детали, которые и хотят услышать:
- закрывает канал отправитель, и только один. Закрытие в получателе даёт панику при следующей записи;
- буфер по числу задач спасает от блокировки, если получатель отвалится раньше времени;
- порядок результатов не гарантирован. Нужен исходный порядок, пишут не в канал, а в срез по индексу: гонки нет, потому что каждая горутина трогает свою ячейку;
- ошибки и отмена:
errgroup.WithContextотменяет остальные задачи при первой ошибке и возвращает её, это стандартный современный способ; - ограничение параллелизма: семафор из буферизованного канала или
g.SetLimit(n), иначе тысяча горутин уронит внешний сервис; - утечка горутин: если получатель перестанет читать небуферизованный канал, отправители повиснут навсегда. Отсюда контекст с таймаутом и
selectнаctx.Done().
Где спотыкаются
Забывают закрыть канал, и range висит вечно.
Пишут в канал внутри цикла без WaitGroup и читают фиксированное число раз, теряя часть результатов при ошибке.
Не ограничивают параллелизм и получают отказ от внешнего API.