Golang Web Crawler解决方案,2个数据竞赛,退出状态66

时间:2017-10-14 21:26:46

标签: go synchronization web-crawler mutex

我一直关注这个solution。当我运行比赛检测器时没有检测到竞争条件。但是,当我使用我的代码运行race Detecter时,它会出现以下错误:

  

==================警告:数据竞争通过goroutine 6读取0x00c42006c1e0:main.Crawl.func1()         /task2.go:50 + 0x53

     

以前通过main goroutine写入0x00c42006c1e0:main.Crawl()         /task2.go:48 + 0x692 main.main()         /task2.go:66 + 0x8c

     

Goroutine 6(正在运行)创建于:main.Crawl()         /task2.go:49 + 0x61e main.main()         /task2.go:66 + 0x8c   ==================。 。 。   ==================警告:数据竞争由goroutine 8读取0x00c420094070:main.Crawl.func1()         /task2.go:50 + 0x53

     

上一篇由goroutine 6写在0x00c420094070:main.Crawl()         /task2.go:48 + 0x692 main.Crawl.func1()         /task2.go:51 + 0x240

     

Goroutine 8(正在运行)创建于:main.Crawl()         /task2.go:49 + 0x61e main.Crawl.func1()         /task2.go:51 + 0x240

     

Goroutine 6(正在运行)创建于:main.Crawl()         /task2.go:49 + 0x61e main.main()

     

/task2.go:66 + 0x8c

     

找到2个数据竞赛退出状态66

以下是我的代码,任何人都可以告诉我哪里出错了。我一直试图弄清楚这么长时间,但无法识别。

        var visited = struct {
        urls map[string]bool
        sync.Mutex
    }{urls: make(map[string]bool)}

    func Crawl(url string, depth int, fetcher Fetcher) {

        if depth <= 0 {
            return
        }

        visited.Lock()
        if visited.urls[url] && visited.urls[url] == true {
            fmt.Println("already fetched: ", url)

            visited.Unlock()
            return
        }
        visited.urls[url] = true
        visited.Unlock()

        body, urls, err := fetcher.Fetch(url)

        if err != nil {
            fmt.Println(err)
            return
        }
        done := make(chan bool)

        for _, nestedUrl := range urls {
            go func(url string, d int) {
                fmt.Printf("-> Crawling child %v of %v with depth %v \n", nestedUrl, url, depth)
                Crawl(url, d, fetcher)
                done <- true

            }(nestedUrl, depth-1)
        }
        for i := range urls {
            fmt.Printf("<- [%v] %v/%v Waiting for child %v.\n", url, i, len(urls))
            <-done
        }
        fmt.Printf("<- Done with %v\n", url)
    }

    func main() {
        Crawl("http://golang.org/", 4, fetcher)

        fmt.Println("Fetching stats\n--------------")

        for url, err := range visited.urls {
            if err != true {
                fmt.Printf("%v failed: %v\n", url, err)
            } else {
                fmt.Printf("%v was fetched\n", url)
            }
        }
  }

1 个答案:

答案 0 :(得分:0)

你正在调用Crawl,它会触发一个go例程来递归,然后你正在访问受访的受保护映射而没有在main中使用互斥锁,这是在一些爬行完成之前执行的。关于风格的几点:

  • 首选同步api
  • 将访问过的结构体置于锁定状态(无公共锁定)
  • 使用主要中的等待组或频道等待完成

所以开始同步,然后弄清楚如何最好地改变为异步。然后,只需在同步爬网功能前放置即可使其同步。看一下原始的巡演,它并不像这个解决方案,所以我不确定这是一个很好的模型。呼叫者不必锁定或担心比赛,因此您需要重新设计。我将从original tour exercise重新开始。

对于锁定,我使用

type T struct {
data map[string]bool
mu sync.Mutex // not just sync.Mutex
}

并且T决定何时需要锁定并具有调整数据状态或搜索数据的功能。这样可以更容易地考虑使用Lock,而不太可能出错。