小编典典

去旅游爬虫运动麻烦

go

我正在进行巡回演出,我觉得除了并发以外,我对这种语言有很好的理解。

幻灯片72上,有一个练习,要求读者并行化Web爬网程序(并使其不覆盖重复的内容,但我还没有到达那里。)

这是我到目前为止的内容:

func Crawl(url string, depth int, fetcher Fetcher, ch chan string) {
    if depth <= 0 {
        return
    }

    body, urls, err := fetcher.Fetch(url)
    if err != nil {
        ch <- fmt.Sprintln(err)
        return
    }

    ch <- fmt.Sprintf("found: %s %q\n", url, body)
    for _, u := range urls {
        go Crawl(u, depth-1, fetcher, ch)
    }
}

func main() {
    ch := make(chan string, 100)
    go Crawl("http://golang.org/", 4, fetcher, ch)

    for i := range ch {
        fmt.Println(i)
    }
}

我的问题是在哪里close(ch)打电话。如果我defer close(ch)Crawl方法中的某个地方放置了一个对象,那么我最终会在其中一个生成的goroutine中写入一个封闭的通道,因为该方法将在生成的goroutine之前完成执行。

如果我省略对close(ch)代码的调用,如我的示例代码所示,则在所有goroutine完成执行后,程序将死锁,但由于从未关闭通道,因此主线程仍在for循环中的通道上等待。


阅读 197

收藏
2020-07-02

共1个答案

小编典典

看一看“
有效执行”的“并行化”部分,可以找到解决方案的想法。本质上,您必须在函数的每个返回路径上关闭通道。实际上,这是defer语句的一个很好的用例:

func Crawl(url string, depth int, fetcher Fetcher, ret chan string) {
    defer close(ret)
    if depth <= 0 {
        return
    }

    body, urls, err := fetcher.Fetch(url)
    if err != nil {
        ret <- err.Error()
        return
    }

    ret <- fmt.Sprintf("found: %s %q", url, body)

    result := make([]chan string, len(urls))
    for i, u := range urls {
        result[i] = make(chan string)
        go Crawl(u, depth-1, fetcher, result[i])
    }

    for i := range result {
        for s := range result[i] {
            ret <- s
        }
    }

    return
}

func main() {
    result := make(chan string)
    go Crawl("http://golang.org/", 4, fetcher, result)

    for s := range result {
        fmt.Println(s)
    }
}

您的代码的本质区别在于,每个Crawl实例都有其自己的返回通道,而调用程序函数则在其返回通道中收集结果。

2020-07-02