Go语言中的并发函数
Go语言是一门并发编程语言,其内置了丰富的并发函数。利用这些函数,我们可以轻松地实现多线程爬虫,提高程序的并发能力和效率。
goroutine
在Go语言中,goroutine是轻量级线程,可以并发执行函数或方法。使用goroutine非常容易,只需要在函数或方法前面加上go关键字即可。例:
func main() {
go foo()
}
func foo() {
// do something
}
上面的代码中,foo函数将以goroutine的形式并发执行。在main函数中调用foo函数时,加上了go关键字,就将foo函数的执行放到了一个新的goroutine中,并发执行。
channel
在并发编程中,常常需要进行多个goroutine之间的通信和同步。Go语言提供了一个内置的chan类型,用于在不同的goroutine之间进行数据传输。
chan类型有发送和接收两个操作,可以使用make函数创建chan对象。例:
ch := make(chan int)
上述代码创建了一个整型类型的无缓存的chan对象。
select关键字
在并发编程中,常常需要多个channel之间进行协作,实现更加复杂的逻辑。此时,Go语言提供了select关键字,可以选择多个channel的读写操作。
select {
case data := <- a:
// process data from a
case data := <- b:
// process data from b
case c <- data:
// send data to c
}
上述代码中,select语句会从a和b两个channel中读取数据,或者将数据写入c通道。select语句会等待其中的某一个操作完成,然后执行对应的分支。如果有多个操作同时可以执行,select会随机选择其中的一个分支执行。
多线程爬虫实现
多线程爬虫是一种常见的并发编程实践,可以快速地抓取大量数据。下面将以Go语言中的goroutine和channel实现多线程爬虫为例。
实现思路
在实现多线程爬虫时,需要如下几个步骤:
1.创建一组URL列表,作为爬虫的启动点。
2.创建一个用于保存已爬取URL的map,避免重复爬取。
3.创建一个channel,用于传输待爬取的URL。
4.创建多个worker goroutine,从channel中读取URL并爬取。
5.将爬取到的数据保存到本地。
具体实现
下面给出一个简单的实现,以爬取百度首页为例。
首先定义一个map,用于保存已爬取的URL以及对应的数据。
var crawled = make(map[string]bool)
var results = make(map[string]string)
然后定义一个待爬取的URL channel,用于传输待爬取的URL。
var urls = make(chan string)
然后创建一个worker函数,用于爬取一个URL并将结果保存到results中。
func worker() {
var url string
for {
url = <- urls
if !crawled[url] {
fmt.Println("Crawling", url)
resp, err := http.Get(url)
if err != nil {
fmt.Println(err)
} else {
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println(err)
} else {
results[url] = string(body)
crawled[url] = true
fmt.Println("Crawled", url)
}
}
}
}
}
上述代码中,worker函数通过从urls channel中读取待爬取的URL,并检查该URL是否已经爬取。如果该URL尚未爬取,则发送HTTP请求并读取响应。处理完响应之后,将结果保存到results中,并将该URL标记为已爬取。
最后,我们需要创建一个爬虫的入口函数,用于添加初始URL,并启动worker goroutine。
func run() {
urls <- "http://www.baidu.com"
for {
select {
case url := <- urls:
go worker(url)
}
}
}
上述代码中,run函数向urls channel中添加初始URL,然后不断从urls channel中读取URL,并启动worker goroutine。
完整代码实现如下:
var crawled = make(map[string]bool)
var results = make(map[string]string)
var urls = make(chan string)
func worker(url string) {
if !crawled[url] {
fmt.Println("Crawling", url)
resp, err := http.Get(url)
if err != nil {
fmt.Println(err)
} else {
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println(err)
} else {
results[url] = string(body)
crawled[url] = true
fmt.Println("Crawled", url)
}
}
}
}
func run() {
urls <- "http://www.baidu.com"
for {
select {
case url := <- urls:
go worker(url)
}
}
}
func main() {
run()
}
可以看到,通过goroutine和channel的使用,我们轻松地实现了一个多线程爬虫,提高了程序的并发效率。
总结
本文介绍了Go语言中的并发函数,包括goroutine、channel和select关键字,并通过一个实例,演示了如何使用这些函数实现多线程爬虫。通过并发编程,我们可以充分利用现代计算机的多核心处理能力,提高程序的并发能力和效率,是一种现代化编程思想的体现。