Go 中的 Goroutine 和通道

我试图理解一个代码示例,它代表 Go 中的多个读者和作者。


此代码示例用于计算一个/多个网页的大小。


代码版本 1:


package main


import (

    "fmt"

    "io/ioutil"

    "net/http"

)


func main() {

    urls := []string{"http://google.com", "http://yahoo.com", "http://reddit.com"}


    sizeCh := make(chan string)

    urlCh := make(chan string)


    for i := 0; i < 3; i++ { //later we change i<3 to i<2

        go worker(urlCh, sizeCh, i)

    }


    for _, u := range urls {

        urlCh <- u //later: go generator(u, urlCh)

    }


    for i := 0; i < len(urls); i++ {

        fmt.Println(<-sizeCh)

    }

}


func worker(urlCh chan string, sizeCh chan string, id int) {

    for {

        url := <-urlCh

        length, err := getPage(url)

        if err == nil {

            sizeCh <- fmt.Sprintf("%s has legth %d. worker %d", url, length, id)

        } else {

            sizeCh <- fmt.Sprintf("Error getting %s: %s. worker %d", url, err, id)

        }

    }

}


func getPage(url string) (int, error) {

    resp, err := http.Get(url)

    if err != nil {

        return 0, err

    }


    defer resp.Body.Close()

    body, err := ioutil.ReadAll(resp.Body)

    if err != nil {

        return 0, err

    }


    return len(body), nil

}

结果:


http://reddit.com has legth 110937. worker 0

http://google.com has legth 18719. worker 2

http://yahoo.com has legth 326987. worker 1

但是在将for i := 0; i < 3; i++(第 15 行)更改为for i := 0; i < 2; i++i < len(urls) 之后,我们没有得到任何结果(总是在等待...)


在[版本 2] 中,我们在版本 1 中添加了一个辅助函数:


func generator(url string, urlCh chan string) {

    urlCh <- url

}

并将第 19-21 行更改为:


for _, u := range urls {

    go generator(u, urlCh)

}

即使使用i<2:


http://google.com has legth 18701. worker 1

http://reddit.com has legth 112469. worker 0

http://yahoo.com has legth 325752. worker 1

为什么版本 1 在条件i<2(即i<len(urls))下失败而版本 2 没有?


aluckdog
浏览 176回答 1
1回答

江户川乱折腾

在您的程序中,您有以下循环遍历 3 个 URL:for _, u := range urls {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;urlCh <- u //later: go generator(u, urlCh)}由于 urlCh 是无缓冲的,循环体中的发送操作将不会完成,直到另一个 Goroutine 执行相应的接收操作。当你有 3 个工作程序 goroutine 时,这没问题。当您将其减少到两个时,这意味着至少有一个 goroutine 需要进展到足够远才能从urlCh.现在,如果我们查看主体,worker我们可以看到问题:for {&nbsp; &nbsp; url := <-urlCh&nbsp; &nbsp; length, err := getPage(url)&nbsp; &nbsp; if err == nil {&nbsp; &nbsp; &nbsp; &nbsp; sizeCh <- fmt.Sprintf("%s has legth %d. worker %d", url, length, id)&nbsp; &nbsp; } else {&nbsp; &nbsp; &nbsp; &nbsp; sizeCh <- fmt.Sprintf("Error getting %s: %s. worker %d", url, err, id)&nbsp; &nbsp; }}在成功发送 上的值之前,此循环无法完成sizeCh。由于这个通道也是无缓冲的,所以在另一个 goroutine 准备好从该通道接收值之前不会发生这种情况。不幸的是,唯一会这样做的 goroutine 是main,它只有在完成将值发送到urlCh. 这样我们就陷入了僵局。移动发送到urlCh分离够程修复了问题,因为main可以进步,它从读取点sizeCh,即使不是所有的价值已被送往urlCh。
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Go