并发获取多个URL
Go
语言最有意思并且最新奇的特性就是对并发编程的支持。并发编程是一个大话题,在第八章和第九章中会专门讲到。这里我们只浅尝辄止地来体验一下 Go
语言里的 goroutine
和 channel
。
下面的例子 fetchall
,和前面小节的 fetch
程序所要做的工作基本一致,fetchall
的特别之处在于它会同时去获取所有的 URL
,所以这个程序的总执行时间不会超过执行时间最长的那一个任务,前面的 fetch
程序执行时间则是所有任务执行时间之和。fetchall
程序只会打印获取的内容大小和经过的时间,不会像之前那样打印获取的内容。
Unresolved include directive in modules/ROOT/pages/ch1/ch1-06.adoc - include::example$/ch1/fetchall/main.go[]
下面使用 fetchall
来请求几个地址:
$ go build gopl.io/modules/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 elapsed
goroutine
是一种函数的并发执行方式,而 channel
是用来在 goroutine
之间进行参数传递。main
函数本身也运行在一个 goroutine
中,而 go function 则表示创建一个新的 goroutine
,并在这个新的 goroutine
中执行这个函数。
main
函数中用 make
函数创建了一个传递 string
类型参数的 channel
,对每一个命令行参数,我们都用 go
这个关键字来创建一个 goroutine
,并且让函数在这个 goroutine
异步执行 http.Get
方法。这个程序里的 io.Copy
会把响应的 Body
内容拷贝到 ioutil.Discard
输出流中(译注:可以把这个变量看作一个垃圾桶,可以向里面写一些不需要的数据),因为我们需要这个方法返回的字节数,但是又不想要其内容。每当请求返回内容时,fetch
函数都会往 ch
这个 channel
里写入一个字符串,由 main
函数里的第二个 for
循环来处理并打印 channel
里的这个字符串。
当一个 goroutine
尝试在一个 channel
上做 send
或者 receive
操作时,这个 goroutine
会阻塞在调用处,直到另一个 goroutine
从这个 channel
里接收或者写入值,这样两个 goroutine
才会继续执行 channel
操作之后的逻辑。在这个例子中,每一个 fetch
函数在执行时都会往 channel
里发送一个值( ch <- expression
),主函数负责接收这些值( <-ch
)。这个程序中我们用 main
函数来完整地处理/接收所有 fetch
函数传回的字符串,可以避免因为有两个 goroutine
同时完成而使得其输出交错在一起的危险。
练习 1.10: 找一个数据量比较大的网站,用本小节中的程序调研网站的缓存策略,对每个URL执行两遍请求,查看两次时间是否有较大的差别,并且每次获取到的响应内容是否一致,修改本节中的程序,将响应结果输出到文件,以便于进行对比。
练习 1.11: 在 fetchall
中尝试使用长一些的参数列表,比如使用在 alexa.com
的上百万网站里排名靠前的。如果一个网站没有回应,程序将采取怎样的行为?( Section8.9 描述了在这种情况下的应对机制)。