我已经基于以下示例创建了一个简单的通道来发出异步HTTP请求:
http://matt.aimonetti.net/posts/2012/11/27/real-life-concurrency-in-go/
一旦所有请求完成,关闭频道的最佳模式是什么?
type HttpRequest struct {
url string
}
type HttpResponse struct {
request HttpRequest
response *http.Response
err error
}
func asyncHttpGets(requests []HttpRequest) {
ch := make(chan *HttpResponse)
for _, request := range requests {
go func(url string) {
resp, err := http.Get(url)
ch <- &HttpResponse{request, resp, err}
}(request.url)
}
for {
select {
case r := <-ch:
processResponse(r)
}
}
}
答案 0 :(得分:5)
这样编写的代码会产生死锁。但是,渠道并不一定要关闭。有多种方法可以解决这个问题。
例如,您可以通过以下方式替换for / select循环:
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
另一个解决方案是使用原子计数器计算goroutines中的响应,并在计数器达到限制时显式关闭通道。但是处理同步/原子通常容易出错,因此这可能不是一个好主意。
最后,有时您需要获得更多控制权才能正确管理超时,错误等... Tomb包可以帮助您以安全的方式管理goroutines的生命周期。