【问题标题】:Exercise: Web Crawler - concurrency not working练习:Web Crawler - 并发不起作用
【发布时间】:2012-09-01 04:46:11
【问题描述】:

我正在浏览 golang 并进行最后一个练习,将网络爬虫更改为并行爬取,而不是重复爬取 (http://tour.golang.org/#73)。我改变的只是爬取功能。

    var used = make(map[string]bool)

    func Crawl(url string, depth int, fetcher Fetcher) {
        if depth <= 0 {
            return
        }
        body, urls, err := fetcher.Fetch(url)
        if err != nil {
            fmt.Println(err)
            return
        }
        fmt.Printf("\nfound: %s %q\n\n", url, body)
        for _,u := range urls {
            if used[u] == false {
                used[u] = true
                Crawl(u, depth-1, fetcher)
            }
        }
        return
    }

为了使其并发,我在调用 Crawl 函数之前添加了 go 命令,但程序没有递归调用 Crawl 函数,只找到“http://golang.org/”页面而没有其他页面。

为什么我在函数Crawl的调用中加入go命令后程序不工作?

【问题讨论】:

    标签: concurrency go


    【解决方案1】:

    问题似乎是,您的进程在所有 URL 都可以被跟踪之前退出 由爬虫。由于并发,main() 过程在之前退出 工人已经完成了。

    要避免这种情况,您可以使用sync.WaitGroup:

    func Crawl(url string, depth int, fetcher Fetcher, wg *sync.WaitGroup) {
        defer wg.Done()
        if depth <= 0 {
             return
        }
        body, urls, err := fetcher.Fetch(url)
        if err != nil {
            fmt.Println(err)
            return
        }
        fmt.Printf("\nfound: %s %q\n\n", url, body)
        for _,u := range urls {
            if used[u] == false {
               used[u] = true
               wg.Add(1)
               go Crawl(u, depth-1, fetcher, wg)
            }
        }
        return
    }
    

    并在main 中调用Crawl,如下所示:

    func main() {
        wg := &sync.WaitGroup{}
    
        Crawl("http://golang.org/", 4, fetcher, wg)
    
        wg.Wait()
    }
    

    另外,don't rely on the map being thread safe

    【讨论】:

    • 使用过的地图在哪里声明?我假设它是 map[string]bool 类型。在这种情况下,您正在“共享”内存进行通信,而不是进行通信以共享内存,因为我相信您的代码依赖于所有 Crawl 调用共享的使用。
    • AFAIR 我使用了问题中的代码,除了添加了WaitGroup 之外没有更改它,只是为了展示它是如何使用的。你是对的,在示例中,地图被假定为线程安全的,但我明确警告不要依赖它。但我同意这可能不会被这样解读,我也没有给出反例。
    • 感谢 nemo 的额外有用评论。这让我明白了。
    • Afaik 您的解决方案存在竞争条件。一些深度为 2 的 url 可能无法获取。如果有一个 url 嵌套太深,但深度为 1,并且 gouroutines 会首先获取嵌套太深的那个,那么它不会被抓取,因为它是当前深度。
    • 请注意,您应该在每次返回之前调用“wg.Done()”,否则在出现错误/深度情况时您将无限期地等待。它是通过在 Crawl 开始时调用 'defer wg.Done()' 来实现的
    【解决方案2】:

    这是一种方法,再次使用sync.WaitGroup,但将 fetch 函数包装在匿名 goroutine 中。为了使 url map 线程安全(意味着并行线程不能同时访问和更改值),应该将 url map 包装在一种新类型中,其中包含 sync.Mutex 类型,即我的示例中的 fetchedUrls 类型和在搜索/更新地图时使用LockUnlock 方法。

    type fetchedUrls struct {
        urls map[string]bool
        mux sync.Mutex
    }
    
    // Crawl uses fetcher to recursively crawl
    // pages starting with url, to a maximum of depth.
    func Crawl(url string, depth int, fetcher Fetcher, used fetchedUrls, wg *sync.WaitGroup) {
        if depth <= 0 {
            return
        }
        used.mux.Lock()
        if used.urls[url] == false {
            used.urls[url] = true
            wg.Add(1)
            go func() {
                defer wg.Done()
                body, urls, err := fetcher.Fetch(url)
                if err != nil {
                    fmt.Println(err)
                    return
                }
                fmt.Printf("found: %s %q\n", url, body)
                for _, u := range urls {
                    Crawl(u, depth-1, fetcher, used, wg)
                }
                return
            }()
        }
        used.mux.Unlock()
        return
    }
    
    func main() {
        wg := &sync.WaitGroup{}
        used := fetchedUrls{urls: make(map[string]bool)}
        Crawl("https://golang.org/", 4, fetcher, used, wg)
        wg.Wait()
    }
    

    输出:

    found: https://golang.org/ "The Go Programming Language"
    not found: https://golang.org/cmd/
    found: https://golang.org/pkg/ "Packages"
    found: https://golang.org/pkg/os/ "Package os"
    found: https://golang.org/pkg/fmt/ "Package fmt"
    
    Program exited.
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-01-24
      • 2019-04-17
      • 1970-01-01
      • 2018-02-08
      • 2015-07-22
      • 1970-01-01
      相关资源
      最近更新 更多