英文:
Closing a channel
问题
我已经创建了一个简单的通道,用于基于以下示例进行异步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)
}
}
}
英文:
I've created a simple channel to make asynchronous HTTP requests based on the following example:
http://matt.aimonetti.net/posts/2012/11/27/real-life-concurrency-in-go/
What would be the best pattern to close the channel, once all the requests have finished?
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)
}
}
}
答案1
得分: 6
这段代码写成这样会导致死锁。但是,通道不一定需要关闭。有多种方法可以解决这个问题。
例如,你可以将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
另一种解决方案是在goroutine中使用原子计数器计算响应,并在计数器达到限制时显式关闭通道。但是,处理sync/atomic通常容易出错,所以在这里可能不是一个好主意。
最后,有时您需要更多的控制权以便正确管理超时、错误等... tomb包可以帮助您以安全的方式管理goroutine的生命周期。
请参阅https://github.com/go-tomb/tomb/tree/v2
英文:
The code, written like this, will produce a deadlock. But, the channel does not have necessarily to be closed. There are multiple ways to solve this issue.
For instance, you could replace the for/select loop by:
n := len(requests)
for r := range ch {
processResponse(r)
n--
if n == 0 {
break
}
}
Here we assume that the potential timeouts are managed in each goroutine.
Another solution, which really relies on closing the channel could be written as follows:
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)
}
}
Note that compared the initial code, the request variable is not accessed from the goroutine, but passed as a parameter. The output data structure posted via the channel is therefore consistent. This was an issue in the initial code. See more information about this specific topic at: https://github.com/golang/go/wiki/CommonMistakes
Yet another solution would be to count the responses in the goroutines using an atomic counter, and explicitly close the channel when the counter reaches the limit. But dealing with sync/atomic is often error-prone, so it is probably not a good idea here.
Finally, sometimes you need to get more control in order to properly manage timeouts, errors, etc ... The tomb package can help you to manage the lifecycle of the goroutines in a safe way.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论