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同时完成造成输出交织锁定带来的风险。