Go中的goroutine和channel使用
2015-11-29 17:22
519 查看
作为一个go的新手,一开始跟着Go指南进行Go的学习,在完成指南上的Web爬虫练习时遇到了一些goroutine与channel相关的问题。
指南上一开始给出了原始代码其中最重要的就是Crawl函数,代码如下:
完成这个功能的难点在于如果依旧使用递归的方式来进行页面抓取,那么子goroutine如何知晓某一url是否已被抓取,一开始,我是用这样的方式实现的:
1、执行Crawl函数的主goroutine通过count来确保在所有的子goroutine都结束运行后再结束运行。
2、所有goroutine通过共享urlsFetched内存来确保抓取的页面不会重复。
go是按照CSP来实现并发的,提倡“通过通信来共享内存,而非通过共享内存来通信”的原则。
上面的代码通过共享内存urlsFetched来进行通信是违背CSP原则的,各goroutine都可以读取并修改urlFetched,这样的设计存在隐患,仍然有可能会重复抓取页面,所以共享内存的方式很危险。
为了遵守CSP原则,我修改了代码,修改后代码如下:
修改后的代码主要的内容就是:
1、不再进行递归
2、只让主goroutine管理urlFetched内存
3、其他子goroutine传回运行结果给主goroutine,主goroutine再启动新的子goroutine
这样便遵守了CSP原则,并且更加安全了。
完整代码如下:
[code]package main
import (
"fmt"
)
type Fetcher interface {
// Fetch 返回 URL 的 body 内容,并且将在这个页面上找到的 URL 放到一个 slice 中。
Fetch(url string) (body string, urls []string, err error)
}
/*
// Crawl 使用 fetcher 从某个 URL 开始递归的爬取页面,直到达到最大深度。
func Crawl(url string, depth int, fetcher Fetcher) {
// TODO: 并行的抓取 URL。
// TODO: 不重复抓取页面。
ch:=make(chan int)
count:=1
urlsFetched:=make(map[string]string)
go CrawlWithConcurrency(url,depth,fetcher,ch,urlsFetched)
for count>0 {
count+=
指南上一开始给出了原始代码其中最重要的就是Crawl函数,代码如下:
// Crawl 使用 fetcher 从某个 URL 开始递归的爬取页面,直到达到最大深度。 func Crawl(url string, depth int, fetcher Fetcher) { // TODO: 并行的抓取 URL。 // TODO: 不重复抓取页面。 // 下面并没有实现上面两种情况: if depth <= 0 { return } body, urls, err := fetcher.Fetch(url) if err != nil { fmt.Println(err) return } fmt.Printf("found: %s %q\n", url, body) for _, u := range urls { Crawl(u, depth-1, fetcher) } return }这段代码很简单,就是递归地Crawl所获得的所有url。题目要求修改Crawl函数使用并发并且不重复抓取页面。
完成这个功能的难点在于如果依旧使用递归的方式来进行页面抓取,那么子goroutine如何知晓某一url是否已被抓取,一开始,我是用这样的方式实现的:
// Crawl 使用 fetcher 从某个 URL 开始递归的爬取页面,直到达到最大深度。 func Crawl(url string, depth int, fetcher Fetcher) { // TODO: 并行的抓取 URL。 // TODO: 不重复抓取页面。 ch:=make(chan int) count:=1 urlsFetched:=make(map[string]string) go CrawlWithConcurrency(url,depth,fetcher,ch,urlsFetched) for count>0 { count+=<-ch } return } func CrawlWithConcurrency(url string, depth int, fetcher Fetcher, ch chan int,urlsFetched map[string]string) { if depth<=0 { ch <- -1 return } body, urls, err := fetcher.Fetch(url) urlsFetched[url]="true" if err != nil { fmt.Println(err) ch <- -1 return } fmt.Printf("found: %s %q\n", url, body) for _,u:=range urls { _,exists:=urlsFetched[u] if exists { continue } go CrawlWithConcurrency(u,depth-1,fetcher,ch,urlsFetched) ch <- 1 } ch <- -1 return }简单地说就是:
1、执行Crawl函数的主goroutine通过count来确保在所有的子goroutine都结束运行后再结束运行。
2、所有goroutine通过共享urlsFetched内存来确保抓取的页面不会重复。
go是按照CSP来实现并发的,提倡“通过通信来共享内存,而非通过共享内存来通信”的原则。
上面的代码通过共享内存urlsFetched来进行通信是违背CSP原则的,各goroutine都可以读取并修改urlFetched,这样的设计存在隐患,仍然有可能会重复抓取页面,所以共享内存的方式很危险。
为了遵守CSP原则,我修改了代码,修改后代码如下:
type Result struct { depth int urls []string } //主goroutine用于控制程序的结束 func Crawl(url string, depth int, fetcher Fetcher) { ch:=make(chan *Result) urlsFetched:=make(map[string]string) count:=1 urlsFetched[url]="true" go CrawlWorker(url,depth,fetcher,ch) for count>0 { result:=<-ch //depth<=1则不crawlresult中的urls if result.depth>1 { for _,u:=range result.urls{ _,exists:=urlsFetched[u] if exists { continue } else { count++ urlsFetched[u]="true" go CrawlWorker(u,result.depth-1,fetcher,ch) } } } count-- } return } func CrawlWorker(url string, depth int, fetcher Fetcher,ch chan *Result) { body, urls, err := fetcher.Fetch(url) if err != nil { fmt.Println(err) ch<-&Result{depth,urls} return } fmt.Printf("found: %s %q\n", url, body) ch<-&Result{depth,urls} }
修改后的代码主要的内容就是:
1、不再进行递归
2、只让主goroutine管理urlFetched内存
3、其他子goroutine传回运行结果给主goroutine,主goroutine再启动新的子goroutine
这样便遵守了CSP原则,并且更加安全了。
完整代码如下:
[code]package main
import (
"fmt"
)
type Fetcher interface {
// Fetch 返回 URL 的 body 内容,并且将在这个页面上找到的 URL 放到一个 slice 中。
Fetch(url string) (body string, urls []string, err error)
}
/*
// Crawl 使用 fetcher 从某个 URL 开始递归的爬取页面,直到达到最大深度。
func Crawl(url string, depth int, fetcher Fetcher) {
// TODO: 并行的抓取 URL。
// TODO: 不重复抓取页面。
ch:=make(chan int)
count:=1
urlsFetched:=make(map[string]string)
go CrawlWithConcurrency(url,depth,fetcher,ch,urlsFetched)
for count>0 {
count+=
相关文章推荐
- Java IO与NIO的一些文件拷贝测试
- Go 语言 Channel 实现原理精要
- SQLSERVER 中GO的作用详解
- 在Go语言程序中使用gojson来解析JSON格式文件
- 举例详解Go语言中os库的常用函数用法
- Go语言中函数的参数传递与调用的基本方法
- 深入解析Go语言的io.ioutil标准库使用
- GO语言的IO方法实例小结
- Go语言的os包中常用函数初步归纳
- Java中channel用法总结
- Go语言中数组的基本用法演示
- go语言channel实现多核并行化运行的方法
- GO语言类型转换和类型断言实例分析
- 深入解析Go语言编程中的递归使用
- 初步解读Golang中的接口相关编写方法
- Go语言实现的最简单数独解法
- 详解Golang编程中的常量与变量
- Go实现比较时间大小
- 深入剖析Go语言编程中switch语句的使用
- 简单讲解Go程序中使用MySQL的方法