Go最令人感兴趣和新颖的特点是支持并发编程。本节将简单了解以下Go主要的并发机制、goroutine和通道
程序fetchall
和前一个一样获取URL
的内容,但是它并发获取很多URL
内容,于是这个进程使用的时间不超过耗时最长时间的获取任务,而不是所有获取任务总的时间。
这个版本的程序将会丢弃响应的内容,但是会打印处每一个响应的大小和花费的时间。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
// fetchall 并发获取URL并打印它们的时间和大小
package main
import (
"fmt"
"io"
"net/http"
"os"
"time"
)
func fetch(url string, ch chan<- string) {
start := time.Now()
res, err := http.Get(url)
if err != nil {
ch <- fmt.Sprint(err)
return
}
nBytes, err := io.Copy(io.Discard, res.Body)
res.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)
}
func main() {
start := time.Now()
ch := make(chan string)
for _, url := range os.Args[1:] {
go fetch(url, ch)
}
for range os.Args[1:] {
fmt.Println(<-ch)
}
fmt.Printf("%.2fs elapsed\n", time.Since(start).Seconds())
}
|
1
2
3
4
|
$ go run main.go https://go.dev https://baidu.com
1.55s 59799 https://go.dev
2.43s 360239 https://baidu.com
2.43s elapsed
|
goroutine
是一个并发执行的函数。通道
是一种是一种允许某一例程向另一个例程传递指定类型的值的通信机制。main
函数在一个goroutine
中执行,然后go语句创建额外的goroutine
。
main
函数使用make创建一个字符串通道。对于每个命令行参数,go语句在第一轮循环中启动一个新的goroutine
,它异步调用fetch
来使用http.Get
获取URL内容。io.Copy
函数读取响应的内容,然后通过撷取io.Discard
输出流进行丢弃。Copy返回字节数以及出现额任何错误。每一个结果返回时,fetch
发送一行汇总信息到通道ch
。main
中的第二轮循环接受并且输出那些汇总行。
当一个goroutine
试图在一个通道上进行发送或接受操作时,它会阻塞,直到另一个goroutine
试图进行接受或发送操作才传递值,并开始处理两个goroutine
。本例中,每一个fetch
在通道ch
上发送一个值(ch <- expression),main
函数接受它们(<- ch)。由main
来处理所有额输出确保了每个goroutine
作为一个整体单元处理,这样就避免了两个goroutine
同时完成造成输出交织锁定带来的风险。