go编程指南的题目
题目链接:https://tour.go-zh.org/concurrency/10
练习:Web 爬虫
在这个练习中,我们将会使用 Go 的并发特性来并行化一个 Web 爬虫。
修改 Crawl
函数来并行地抓取 URL,并且保证不重复。
提示:你可以用一个 map 来缓存已经获取的 URL,但是要注意 map 本身并不是并发安全的!
1 | package main |
输出:
1 | found: https://golang.org/ "The Go Programming Language" |
注意其中的并发写法:
1 | go func(url string) { |
需要将url
传入,而不能采用如下的写法:
1 | go func() { |
原因是起来之后都共用了同一个外层的u
变量,而这个u
变量是受到for
循环的影响的,导致输出结果的减少。
其中,协程必须要用一个阻塞等待协程运行结束,否则协程会直接被杀死。
当然也可以用别的方法等待
比如在main函数用个死循环
1 | // Crawl 使用 fetcher 从某个 URL 开始递归的爬取页面,直到达到最大深度。 |
使用sync.Map
需要并发读写时,一般的做法是加锁,但这样性能并不高,Go语言在 1.9 版本中提供了一种效率较高的并发安全的 sync.Map,sync.Map 和 map 不同,不是以语言原生形态提供,而是在 sync 包下的特殊结构。
sync.Map 有以下特性:
- 无须初始化,直接声明即可。
- sync.Map 不能使用 map 的方式进行取值和设置等操作,而是使用 sync.Map 的方法进行调用,Store 表示存储,Load 表示获取,Delete 表示删除。
- 使用 Range 配合一个回调函数进行遍历操作,通过回调函数返回内部遍历出来的值,Range 参数中回调函数的返回值在需要继续迭代遍历时,返回 true,终止迭代遍历时,返回 false。
1 | package main |
使用sync.WaitGroup
WaitGroup能够一直等到所有的goroutine执行完成,并且阻塞主线程的执行,直到所有的goroutine执行完成。
WaitGroup总共有三个方法:Add(delta int),Done(),Wait()。简单的说一下这三个方法的作用。
Add:添加或者减少等待goroutine的数量;
Done:相当于Add(-1);
Wait:执行阻塞,直到所有的WaitGroup数量变成 0
这边使用WaitGroup替代channel进行阻塞等待
1 | package main |