Go中的Goroutines和频道

时间:2015-09-17 02:31:11

标签: go

我试图理解代表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++之后,namly i&lt; 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没有?

1 个答案:

答案 0 :(得分:2)

在你的程序中,你有以下循环迭代3个URL:

for _, u := range urls {
         urlCh <- u //later: go generator(u, urlCh)
}

由于urlCh是无缓冲的,因此循环体中的发送操作将不会完成,直到另一个Goroutine执行相应的接收操作。

当你有3个工人goroutines时,这没问题。当你将其减少到2时,这意味着至少有一个goroutine需要进展到足以从urlCh获得第二个值。

现在,如果我们查看worker的正文,我们可以看到问题:

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)
    }
}

sizeCh成功发送值之前,此循环无法完成。由于此通道也是无缓冲的,因此只有在另一个goroutine准备从该通道接收值时才会发生这种情况。

不幸的是,唯一能做到这一点的goroutine是main,只有在完成向urlCh发送值时才会这样做。因此我们陷入僵局。

将发送内容移至urlCh以分隔goroutine可以解决问题,因为main可以进展到sizeCh的读取点,即使并非所有值都已发送到{ {1}}。