код, написанные, как это, произведет тупик. Но канал не обязательно должен быть закрыт. Существует несколько способов решения этой проблемы.
Например, вы могли бы заменить на/выберите цикл по:
n := len(requests)
for r := range ch {
processResponse(r)
n--
if n == 0 {
break
}
}
Здесь мы предполагаем, что потенциальные таймауты управляются в каждом goroutine.
Другое решение, которое действительно полагается на закрытие канала может быть записана следующим образом:
func asyncHttpGets(requests []HttpRequest) {
ch := make(chan *HttpResponse)
var wg sync.WaitGroup
for _, request := range requests {
wg.Add(1)
go func(r HttpRequest) {
defer wg.Done()
resp, err := http.Get(r.url)
ch <- &HttpResponse{r, resp, err}
}(request)
}
go func() {
wg.Wait()
close(ch)
}()
for r := range ch {
processResponse(r)
}
}
Обратите внимание, что по сравнению исходный код, переменная запроса не доступен из goroutine, но передается в качестве параметра. Таким образом, структура выходных данных, размещаемых через канал, является последовательной. Это было проблемой в исходном коде. Дополнительную информацию об этой теме см. По адресу: https://github.com/golang/go/wiki/CommonMistakes
Еще одно решение - подсчитать ответы в горутах с использованием атомного счетчика и явно закрыть канал, когда счетчик достигнет предела. Но работа с sync/atomic часто подвержена ошибкам, поэтому, вероятно, это не очень хорошая идея.
Наконец, иногда вам нужно получить больше контроля, чтобы правильно управлять тайм-аутами, ошибками и т. Д. Пакет гробницы может помочь вам безопасно управлять жизненным циклом goroutines.
См https://github.com/go-tomb/tomb/tree/v2
Awesome, спасибо за подробный ответ. – patrickandroid