Golang 第一章 6.并发获取URL网页内容

  • By v2ray节点

  • 2024-06-29 11:43:33

  • 评论

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节描述了应对这种情况的机制。)


v2ray节点购买