Как определить, что все горутины завершены и все данные получены в Go
асинхронностьгонки данныхгорутинымногопоточностьсинхронизация
Что проверяют
Различаете ли вы два разных факта: горутины закончили работу и результаты собраны. Их путают, и получается либо потеря данных, либо вечное ожидание.
Как отвечать
Завершение горутин отслеживает группа ожидания, а получение всех данных обеспечивается закрытием канала результатов.
results := make(chan Result, len(tasks))
var wg sync.WaitGroup
for _, t := range tasks {
wg.Add(1)
go func() { defer wg.Done(); results <- process(t) }()
}
go func() { wg.Wait(); close(results) }() // закрывает тот, кто дождался всех
for r := range results { // цикл завершится сам после закрытия
collect(r)
}Почему именно так:
wg.Wait()в отдельной горутине: если вызвать его в основном потоке до чтения, при небуферизованном канале получится взаимная блокировка, потому что отправители ждут читателя, а читатель ждёт их завершения;- закрытие канала это сигнал «данных больше не будет», и только он позволяет
rangeзавершиться; - буфер размером с число задач снимает зависимость от скорости чтения.
Дальше стоит назвать варианты:
errgroup:g.Wait()возвращает первую ошибку и дожидается всех, а контекст отменяет остальные задачи. Собирать результаты можно в срез по индексу, тогда канал вообще не нужен и порядок сохраняется;- счётчик вместо канала: если результатов ровно N, можно прочитать ровно N раз, но это хрупко: одна упавшая горутина оставит читателя ждать навсегда;
- таймаут: контекст с ограничением по времени спасает от вечного ожидания, если задача зависла.
Где спотыкаются
Не закрывают канал и получают вечный range.
Вызывают Wait до чтения из небуферизованного канала и ловят взаимную блокировку.
Считают, что завершение горутин означает, что все результаты уже прочитаны.