2016-02-15 03:06:34 +00:00
|
|
|
|
## 1.6. 并发获取多个URL
|
2015-12-09 07:45:11 +00:00
|
|
|
|
|
2016-02-15 03:06:34 +00:00
|
|
|
|
Go语言最有意思并且最新奇的特性就是对并发编程的支持。并发编程是一个大话题,在第八章和第九章中会专门讲到。这里我们只浅尝辄止地来体验一下Go语言里的goroutine和channel。
|
2015-12-09 07:45:11 +00:00
|
|
|
|
|
2016-02-15 03:06:34 +00:00
|
|
|
|
下面的例子fetchall,和前面小节的fetch程序所要做的工作基本一致,fetchall的特别之处在于它会同时去获取所有的URL,所以这个程序的总执行时间不会超过执行时间最长的那一个任务,前面的fetch程序执行时间则是所有任务执行时间之和。fetchall程序只会打印获取的内容大小和经过的时间,不会像之前那样打印获取的内容。
|
2015-12-09 07:45:11 +00:00
|
|
|
|
|
2016-01-20 13:08:13 +00:00
|
|
|
|
<u><i>gopl.io/ch1/fetchall</i></u>
|
2015-12-09 07:45:11 +00:00
|
|
|
|
```go
|
|
|
|
|
// Fetchall fetches URLs in parallel and reports their times and sizes.
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
import (
|
2015-12-23 05:05:29 +00:00
|
|
|
|
"fmt"
|
|
|
|
|
"io"
|
|
|
|
|
"io/ioutil"
|
|
|
|
|
"net/http"
|
|
|
|
|
"os"
|
|
|
|
|
"time"
|
2015-12-09 07:45:11 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func main() {
|
2015-12-23 05:05:29 +00:00
|
|
|
|
start := time.Now()
|
|
|
|
|
ch := make(chan string)
|
|
|
|
|
for _, url := range os.Args[1:] {
|
|
|
|
|
go fetch(url, ch) // start a goroutine
|
|
|
|
|
}
|
|
|
|
|
for range os.Args[1:] {
|
|
|
|
|
fmt.Println(<-ch) // receive from channel ch
|
|
|
|
|
}
|
|
|
|
|
fmt.Printf("%.2fs elapsed\n", time.Since(start).Seconds())
|
2015-12-09 07:45:11 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func fetch(url string, ch chan<- string) {
|
2015-12-23 05:05:29 +00:00
|
|
|
|
start := time.Now()
|
|
|
|
|
resp, err := http.Get(url)
|
|
|
|
|
if err != nil {
|
|
|
|
|
ch <- fmt.Sprint(err) // send to channel ch
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
nbytes, err := io.Copy(ioutil.Discard, resp.Body)
|
|
|
|
|
resp.Body.Close() // don't leak resources
|
|
|
|
|
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)
|
2015-12-09 07:45:11 +00:00
|
|
|
|
}
|
|
|
|
|
```
|
2015-12-23 05:05:29 +00:00
|
|
|
|
|
2016-02-15 03:06:34 +00:00
|
|
|
|
下面使用fetchall来请求几个地址:
|
2015-12-09 07:45:11 +00:00
|
|
|
|
|
2015-12-23 05:05:29 +00:00
|
|
|
|
```
|
2015-12-09 07:45:11 +00:00
|
|
|
|
$ 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 elapsed
|
|
|
|
|
```
|
|
|
|
|
|
2016-02-15 03:06:34 +00:00
|
|
|
|
goroutine是一种函数的并发执行方式,而channel是用来在goroutine之间进行参数传递。main函数本身也运行在一个goroutine中,而go function则表示创建一个新的goroutine,并在这个新的goroutine中执行这个函数。
|
2015-12-23 05:05:29 +00:00
|
|
|
|
|
2016-02-15 03:06:34 +00:00
|
|
|
|
main函数中用make函数创建了一个传递string类型参数的channel,对每一个命令行参数,我们都用go这个关键字来创建一个goroutine,并且让函数在这个goroutine异步执行http.Get方法。这个程序里的io.Copy会把响应的Body内容拷贝到ioutil.Discard输出流中(译注:可以把这个变量看作一个垃圾桶,可以向里面写一些不需要的数据),因为我们需要这个方法返回的字节数,但是又不想要其内容。每当请求返回内容时,fetch函数都会往ch这个channel里写入一个字符串,由main函数里的第二个for循环来处理并打印channel里的这个字符串。
|
2015-12-09 07:45:11 +00:00
|
|
|
|
|
2016-02-15 03:06:34 +00:00
|
|
|
|
当一个goroutine尝试在一个channel上做send或者receive操作时,这个goroutine会阻塞在调用处,直到另一个goroutine往这个channel里写入、或者接收值,这样两个goroutine才会继续执行channel操作之后的逻辑。在这个例子中,每一个fetch函数在执行时都会往channel里发送一个值(ch <- expression),主函数负责接收这些值(<-ch)。这个程序中我们用main函数来接收所有fetch函数传回的字符串,可以避免在goroutine异步执行还没有完成时main函数提前退出。
|
2015-12-09 07:45:11 +00:00
|
|
|
|
|
2016-02-15 03:06:34 +00:00
|
|
|
|
**练习 1.10:** 找一个数据量比较大的网站,用本小节中的程序调研网站的缓存策略,对每个URL执行两遍请求,查看两次时间是否有较大的差别,并且每次获取到的响应内容是否一致,修改本节中的程序,将响应结果输出,以便于进行对比。
|