Golang 第一章 6.并发获取URL网页内容
Go语言中最有趣和新颖的一个方面是其支持并发编程。这是一个广泛的主题,第8章和第9章专门讨论了这个主题,所以现在我们只会简单介绍一下Go的主要并发机制,即goroutines(协程)和channels(通道)。
下一个程序,fetchall,与前面的示例一样,获取URL的内容,但它并发地获取多个URL,这样处理时间将不会超过最长的获取时间,而不是所有获取时间的总和。这个版本的fetchall会丢弃响应内容,但会报告每个URL的大小和消耗的时间:
gopl.io/ch1/fetchall
// 并行获取多个URL,并报告它们的时间和大小
package main
import (
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"time"
)
func main() {
start := time.Now()
ch := make(chan string)
for _, url := range os.Args[1:] {
go fetch(url, ch) // 启动一个 goroutine
}
for range os.Args[1:] {
fmt.Println(<-ch) // 从频道 ch 接收
}
fmt.Printf("%.2fs elapsed\n", time.Since(start).Seconds())
}
func fetch(url string, ch chan<- string) {
start := time.Now()
resp, err := http.Get(url)
if err != nil {
ch <- fmt.Sprint(err) // 发送到频道 ch
return
}
nbytes, err := io.Copy(ioutil.Discard, resp.Body)
resp.Body.Close() // 关闭资源
if err != nil {
ch <- fmt.Sprintf("while reading %s: %v", url, err)
return
}
secs := time.Since(start).Seconds()
ch <- fmt.Sprintf("%.2fs %7d %s", secs, nbytes, url)
}
运行例子:
$ go build gopl.io/ch1/fetchall
$ ./fetchall https://golang.org http://gopl.io https://godoc.org
0.14s 6852 https://godoc.org
0.16s 7261 https://golang.org
0.48s 2475 http://gopl.io
0.48s elap
协程是并发执行的函数。通道是一种通信机制,允许一个协程向另一个协程传递指定类型的值。主函数在一个协程中运行,并且通过 go
语句创建额外的协程。
主函数使用 make
创建了一个字符串通道。对于每个命令行参数,第一个 range
循环中的 go
语句会启动一个新的协程,异步调用 fetch
函数来使用 http.Get
获取URL。io.Copy
函数读取响应的主体并通过写入到 ioutil.Discard
输出流来丢弃它。Copy
函数返回字节数及发生的任何错误。每当结果到达时,fetch
在通道 ch
上发送一个摘要行。主函数中的第二个 range
循环接收并打印这些行。
当一个协程尝试在通道上发送或接收数据时,它会阻塞直到另一个协程尝试进行对应的接收或发送操作,此时值会被传输,然后两个协程都会继续执行。在这个例子中,每个 fetch
都在通道 ch
上发送一个值 (ch <- expression
),而 main
协程接收所有这些值 (<-ch
)。通过让主函数处理所有的打印操作,确保了每个协程的输出作为一个单元进行处理,如果两个协程同时完成,不会出现交错的情况。
练习 1.10: 找到一个生成大量数据的网站。运行两次连续的 fetchall 程序来研究缓存,看看报告的时间是否有很大变化。每次获取到的内容是否相同?修改 fetchall 将其输出打印到文件中,以便进行检查。
练习 1.11: 尝试使用更长的参数列表运行 fetchall
,例如来自 alexa.com 的前一百万个网站样本。如果一个网站没有响应,程序会如何表现?(第八章.9节描述了应对这种情况的机制。)
评论