Golang:关闭一个频道

时间:2014-12-20 10:15:15

标签: concurrency go 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)
        }
    }
}

1 个答案:

答案 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的生命周期。

请参阅https://github.com/go-tomb/tomb/tree/v2