func main() {
fmt.Printf("=== Serial===\n")
Serial("http://golang.org/", fetcher, make(map[string]bool))
fmt.Printf("=== ConcurrentMutex ===\n")
ConcurrentMutex("http://golang.org/", fetcher, makeState())
fmt.Printf("=== ConcurrentChannel ===\n")
ConcurrentChannel("http://golang.org/", fetcher)
}
这里有三段代码,第一段是串行化的爬虫程序:就是经典的DFS操作,第二个是用锁同步的并行程序,第三个是用管道同步
func Serial(url string, fetcher Fetcher, fetched map[string]bool) {
// 如果这个url已经搜索过,直接返回
if fetched[url] {
return
}
fetched[url] = true
urls, err := fetcher.Fetch(url)
if err != nil {
return
}
for _, u := range urls {
Serial(u, fetcher, fetched)
}
return
}
其中fetcher是一个函数接口,fetched是一个map用来记录url是否已经被访问过。
type Fetcher interface {
// Fetch returns a slice of URLs found on the page.
Fetch(url string) (urls []string, err error)
}
仔细研究这个函数调用
fmt.Printf("=== ConcurrentMutex ===\n")
ConcurrentMutex("http://golang.org/", fetcher, makeState())
func makeState() *fetchState {
f := &fetchState{}
f.fetched = make(map[string]bool)
return f
}
makeState返回了一个fetchState的指针,fetchState的结构是这样的。带了一把锁
type fetchState struct {
mu sync.Mutex
fetched map[string]bool
}
map是共享变量,并发访问的时候需要加锁
master执行完认为后,启动若干个worker来做后续工作,他自己要等待。携程之间通过加锁map同步。启动携程的时候用了函数式编程的技巧。
func ConcurrentMutex(url string, fetcher Fetcher, f *fetchState) {
f.mu.Lock()
already := f.fetched[url]
f.fetched[url] = true
f.mu.Unlock()
if already {
return
}
urls, err := fetcher.Fetch(url)
if err != nil {
return
}
var done sync.WaitGroup
for _, u := range urls {
done.Add(1)
u2 := u
go func() {
defer done.Done()
ConcurrentMutex(u2, fetcher, f)
}()
//go func(u string) {
// defer done.Done()
// ConcurrentMutex(u, fetcher, f)
//}(u)
}
done.Wait()
return
}
第三部分就是通过chanel的并发
fmt.Printf("=== ConcurrentChannel ===\n")
ConcurrentChannel("http://golang.org/", fetcher)
数据用管道就行通信
func ConcurrentChannel(url string, fetcher Fetcher) {
ch := make(chan []string)
go func() {
ch <- []string{url}
}()
master(ch, fetcher)
}
func master(ch chan []string, fetcher Fetcher) {
n := 1
fetched := make(map[string]bool)
for urls := range ch {
for _, u := range urls {
if fetched[u] == false {
fetched[u] = true
n += 1
go worker(u, ch, fetcher)
}
}
n -= 1
if n == 0 {
break
}
}
}
work干活,不断的把数据写入到管道中,这里有点难理解。
func worker(url string, ch chan []string, fetcher Fetcher) {
urls, err := fetcher.Fetch(url)
if err != nil {
ch <- []string{}
} else {
ch <- urls
}
}