Почему для закрытия канала и ожидания работы горутин используется отдельная горутина, а не main функция?
Что проверяют
Понимаете ли вы, почему wg.Wait() нельзя звать там же, где вы читаете канал. Это вопрос про взаимную блокировку, и правильный ответ начинается не с чистоты кода, а с того, кто кого держит.
Как отвечать
Разберите порядок шагов. Воркеры пишут в канал и уменьшают счётчик группы ожидания. Потребитель читает канал циклом range, а этот цикл завершается только после закрытия канала. Закрыть канал можно лишь тогда, когда все отправители закончили, то есть после wg.Wait().
Если поставить wg.Wait() в main перед циклом, программа встанет: канал небуферизованный, первый же воркер блокируется на отправке, потому что читать некому, счётчик не обнулится, и Wait не вернётся. Рантайм обнаружит, что все горутины спят, и завершит процесс сообщением о дедлоке. Буфер проблему не решает, а лишь отодвигает до момента, когда он заполнится.
Отсюда приём: ожидание уводят в отдельную горутину, а main остаётся свободным для чтения.
go func() {
wg.Wait()
close(results)
}()
for r := range results {
fmt.Println(r)
}Дополните правилами: закрывает всегда отправляющая сторона и ровно один раз. Для отмены и таймаута берут context, а удобная обёртка над этим приёмом это errgroup, чей Wait ещё и возвращает первую ошибку.
Где спотыкаются
Объясняют приём тем, что close в main якобы вызывает панику. Место вызова ни при чём: паникует запись в уже закрытый канал и повторное закрытие. Проблема именно во взаимной блокировке чтения и ожидания.
Второе: закрывают канал в каждом воркере или со стороны читателя. Закрытие это сообщение о том, что данных больше не будет, и знать это может только отправляющая сторона, причём одна.
Третье: вызывают wg.Add(1) внутри запускаемой горутины. Счётчик может не успеть вырасти до вызова Wait, канал закроется раньше времени, и читатель получит неполный результат. Add вызывают до go.