Go编程语言一书示例中的goroutine泄漏

我正在阅读 The Go Programming Language 一书,书中有一个例子说明了 goroutine 泄漏


func mirroredQuery() string {
    responses := make(chan string, 3)
    go func() { responses <- request("asia.gopl.io") }()
    go func() { responses <- request("europe.gopl.io") }()
    go func() { responses <- request("americas.gopl.io") }()
    return <-responses // return the quickest response
}
func request(hostname string) (response string) { /* ... */ }

我试图解决泄漏,并得到以下代码

func request(url string) string {
    res, err := http.Get(url)
    if err == nil {
        body, err := io.ReadAll(res.Body)
        if err == nil {
            return string(body)
        } else {
            return err.Error()
        }
    } else {
        return err.Error()
    }
}

func getany() string {
    rsp := make(chan string, 3)
    done := make(chan struct{}, 3)
    doRequest := func(url string) {
        select {
            case rsp <- request(url):
                fmt.Printf("get %sn", url)
                done <- struct{}{}
            case <- done:
                fmt.Printf("stop %sn", url)
                return
        }
    }
    go doRequest("http://google.com")
    go doRequest("http://qq.com")
    go doRequest("http://baidu.com")
    return <-rsp
}

但似乎并没有解决问题?有什么建议?

回答

提供的代码中没有 goroutine 泄漏。该mirroredQuery方法使用缓冲通道来收集结果并返回第一个答案。并且当前缓冲区有足够的空间来收集所有 goroutine 的所有答案,即使其余的响应从未被读取。如果缓冲区小于 N - 1,情况就会改变,其中 N 是生成的 goroutine 的数量。在这种情况下,一些生成的 goroutinemirroredQuery会在尝试向responses通道发送响应时卡住。重复调用mirroredQuery会导致卡住的 goroutines 增加,可以称为 goroutines 泄漏。

这是添加了日志的代码和两种方案的输出。

func mirroredQuery() string {
    responses := make(chan string, 2)
    go func() {
        responses <- request("asia.gopl.io")
        log.Printf("Finished goroutine asia.gopl.ion")
    }()
    go func() {
        responses <- request("europe.gopl.io")
        log.Printf("Finished goroutine europe.gopl.ion")
    }()
    go func() {
        responses <- request("americas.gopl.io")
        log.Printf("Finished goroutine americas.gopl.ion")
    }()
    return <-responses // return the quickest response
}
func request(hostname string) (response string) {
    duration := time.Duration(rand.Int63n(5000)) * time.Millisecond
    time.Sleep(duration)
    return hostname
}

func main() {
    rand.Seed(time.Now().UnixNano())
    result := mirroredQuery()
    log.Printf("Fastest result for %sn", result)
    time.Sleep(6*time.Second)
}

缓冲区大小 >= N-1 的输出

2021/06/26 16:05:27 Finished europe.gopl.io
2021/06/26 16:05:27 Fastest result for europe.gopl.io
2021/06/26 16:05:28 Finished asia.gopl.io
2021/06/26 16:05:30 Finished americas.gopl.io

Process finished with the exit code 0

缓冲区大小 < N-1 的输出

2021/06/26 15:47:54 Finished europe.gopl.io
2021/06/26 15:47:54 Fastest result for europe.gopl.io

Process finished with the exit code 0

上面的实现可以通过在第一个响应到达时引入 goroutines 终止来“改进”。这可能会减少已用资源的数量。这在很大程度上取决于request做什么方法。对于计算量大的场景来说,这是有道理的,取消 http 请求可能会导致连接终止,因此下一个请求必须打开新的请求。对于高负载的服务器,即使不使用响应,它也可能不如等待响应有效。

下面是改进的context使用实现。

func mirroredQuery() string {
    responses := make(chan string, 2)
    go func() {
        responses <- request("asia.gopl.io")
        log.Printf("Finished goroutine asia.gopl.ion")
    }()
    go func() {
        responses <- request("europe.gopl.io")
        log.Printf("Finished goroutine europe.gopl.ion")
    }()
    go func() {
        responses <- request("americas.gopl.io")
        log.Printf("Finished goroutine americas.gopl.ion")
    }()
    return <-responses // return the quickest response
}
func request(hostname string) (response string) {
    duration := time.Duration(rand.Int63n(5000)) * time.Millisecond
    time.Sleep(duration)
    return hostname
}

func main() {
    rand.Seed(time.Now().UnixNano())
    result := mirroredQuery()
    log.Printf("Fastest result for %sn", result)
    time.Sleep(6*time.Second)
}


以上是Go编程语言一书示例中的goroutine泄漏的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>