这个Exercise已经把功能框架写好了,只需要额外实现:

  1. 每次分析一个URL时,并发处理该URL下所有子URL。实现的时候,将原有的Crawl用一个匿名func包起来,并在Crawl后面加一个chan写入(解除后面对应的chan读取阻塞),然后在for的外层加一个和for相同次数的chan读取来阻塞,从而等待并发完成。
  2. 抓取每一个URL时,不能抓取重复的URL。新建一个带sync.Mutex的struct(fetched),标记每个URL之前是否抓取过,把fetched读、写的代码lock即可。

主要代码如下(Exercise里面其他原有代码不动,替换如下代码即可):

 1 var fetched = struct {
 2     sync.Mutex
 3     urlFetched map[string]bool
 4 }{urlFetched: make(map[string]bool)}
 5 
 6 func Crawl(url string, depth int, fetcher Fetcher) {
 7     fetched.Lock()
 8     _, ok := fetched.urlFetched[url]
 9     fetched.urlFetched[url] = true
10     fetched.Unlock()
11 
12     if ok || depth <= 0 {
13         return
14     }
15 
16     body, urls, err := fetcher.Fetch(url)
17     if err != nil {
18         fmt.Println(err)
19         return
20     }
21     fmt.Printf("found: %s %q\n", url, body)
22     done := make(chan bool)
23     for _, u := range urls {
24         go func(url string) {
25             Crawl(url, depth-1, fetcher)
26             done <- true
27         }(u)
28     }
29 
30     for range urls {
31         <-done
32     }
33 }

注意,使用go func启动goroutine时,如果main结束了,其他所有goroutine也会结束,所以需要通过阻塞main的goroutine来等待其他完成。