这个Exercise已经把功能框架写好了,只需要额外实现:
- 每次分析一个URL时,并发处理该URL下所有子URL。实现的时候,将原有的Crawl用一个匿名func包起来,并在Crawl后面加一个chan写入(解除后面对应的chan读取阻塞),然后在for的外层加一个和for相同次数的chan读取来阻塞,从而等待并发完成。
- 抓取每一个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来等待其他完成。