当前位置: 首页 > article >正文

【go语言圣经1.6】

目标

学习如何利用 Go 语言的 goroutine 和 channel 实现并发网络请求;

理解如何使用 io.Copy 进行流式数据传输,从而避免一次性申请大缓冲区;

掌握如何通过时间测量和错误传递来构建一个健壮、并发友好的网络请求程序。

总之学习Go 语言并发编程的基础,为探索更复杂的并发场景和高级特性打基础。

概念

要点(案例)

这段代码实现了一个并发网络请求程序,其核心目标是同时发起多个 HTTP 请求,并在所有请求结束后报告每个请求的响应大小以及耗时。整体设计思路包括:

  • 并发执行:利用 Go 的 goroutine 机制同时发起多个网络请求,使程序总执行时间接近单个耗时最长的请求,而不是所有请求时间的总和。
  • 结果同步:通过 channel 在各个 goroutine 与主函数之间传递结果,确保最后输出时不会出现输出交错的问题。
  • 性能监控:通过记录程序开始时间和每个请求的耗时,直观地展示并发请求的效率。
  1. 主函数

    func main() {
    	start := time.Now() // 记录程序开始时间
    	ch := make(chan string) // 建一个用于传递字符串的channel
    	
    	// 针对命令行传入的每个URL,启动一个新的goroutine并发执行fetch函数
    	for _,url := range os.Args[1:] {
    		go fetch(url, ch) // 使用go关键字启动goroutine
    	}
    	
    	// 循环接收所有goroutine传回的结果,保证每个请求都被处理
       for range os.Args[1:] {
            fmt.Println(<-ch)         // 从channel中接收结果并打印
       }
       
       // 打印整个程序的耗时,单位为秒
       fmt.Printf("%.2fs elapsed\n", time.Since(start).Seconds())
    	
    }
    
    • goroutine
      • 是 Go 语言中轻量级的并发执行单元,可以理解为比线程更小的执行体。启动一个 goroutine 非常简单,只需在函数调用前加上 go 关键字。
        • go fetch(url, ch) 表示为每个 URL 启动一个新的并发执行实例,彼此之间互不干扰。
    • channel
      • 是一种用于不同 goroutine 之间通信的工具,通过发送和接收数据来进行同步。
      • 本例中创建了一个 chan string,用于在各个并发任务与主函数之间传递结果。
      • 发送(ch <- ...)和接收(<-ch)操作都是阻塞的,保证数据在通信过程中不会丢失或混乱。
  2. 并发函数fetch

    func fetch(url string, ch chan<- string) {
    
    	start := time.Now()
    	
    	// 发起HTTP GET请求
      resp, err := http.Get(url)
      if err != nil {
          ch <- fmt.Sprint(err)         // 出错时,将错误信息发送到channel中
          return
      }
      // 使用 io.Copy 将响应体拷贝到 ioutil.Discard(垃圾桶),只关心字节数
      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()
    
      // 将(耗时、字节数、URL)发送到channel中
      ch <- fmt.Sprintf("%.2fs  %7d  %s", secs, nbytes, url)
      
    	
    }
    
    • ioutil.Discard:其实是一个实现了 io.Writer 接口的对象,它会丢弃写入的数据,相当于一个“黑洞”。这样做的目的是:我们只关心数据传输的字节数,而不需要保存数据本身。
    • 与一次性将所有数据读取到一个缓冲区相比,io.Copy 会采用分块(chunk)的方式读取数据,这样能有效降低内存使用,尤其是在响应体较大的情况下。
    • 将耗时、读取的字节数以及 URL 格式化为字符串,通过 channel 发送回主函数。这种方式使得各个并发任务的输出能在主函数中统一处理,避免多个 goroutine 同时输出导致的混乱。

语言特性

  1. goroutine 的并发执行
  • 每个 go fetch(url, ch) 都会在一个独立的执行流中运行,不会阻塞主函数。这意味着所有网络请求是同时进行的,程序总执行时间受限于最慢的请求,而不是所有请求的累加时间。
  1. channel 的阻塞特性
    • 发送阻塞:当一个 goroutine 试图发送数据到 channel 而没有接收者时,它会阻塞,直到有其他 goroutine 从 channel 接收数据。
    • 接收阻塞:同理,接收操作在 channel 为空时会阻塞,直到有数据可取。
    • 在本例中,主函数通过循环接收(<-ch)确保每个 fetch 的结果都被正确处理。这样既实现了任务同步,又避免了输出顺序混乱。
  2. 错误传递
    • 每个 fetch 中的错误都会通过 channel 传递到主函数,这种方式使得错误处理集中且易于管理

总结与反思

  • 并发执行实例

    指的是程序中可以同时运行的多个任务或执行单元。这些任务可以独立执行,共享程序的资源,并在某种程度上并行或交替运行。

  • goroutine

    是 Go 语言中用于实现并发执行的核心概念。它是一种轻量级的执行单元,由 Go 运行时调度管理,相比于传统的线程开销更低。每个 goroutine 都可以看作是一个并发执行实例,它们共同构成了程序的并发部分。

  • 每个通过 go 关键字启动的函数调用就是一个 goroutine,也就是一个并发执行实例。Go 语言利用 goroutine 提供了简单而高效的并发模型,让你可以轻松启动成百上千的并发任务,而不必担心传统线程所带来的高昂开销。


http://www.kler.cn/a/589025.html

相关文章:

  • 19.如何使用 pandas 处理大型 Excel 文件:并行读取工作表
  • pytorch小记(八):pytorch中有关于.detach()的浅显见解
  • PostgreSQL技术内幕26:PG聚合算子实现分析
  • 【VBA】excel获取股票实时行情(历史数据,基金数据下载)
  • 量子计算与医疗诊断的未来:超越传统的无限可能
  • 深度学习篇---Opencv中Haar级联分类器的自定义
  • Java 大视界 -- 基于 Java 的大数据机器学习模型的迁移学习应用与实践(129)
  • 五大基础算法——贪心算法
  • 各省水资源平台 水资源遥测终端机都用什么协议
  • CSS中绝对定位
  • 【报错问题】在visual studio 终端使用npm -v后报错禁止运行脚本怎么处理
  • 大三下找C++开发实习的感受分享
  • 网络通信(传输层协议:TCP/IP ,UDP):
  • ADA-YOLO模型深度解析 | 自适应动态注意力驱动的目标检测新范式
  • 本地部署Deep Seek-R1,搭建个人知识库——笔记
  • 【Go每日一练】计算整数数组的最大子数组和
  • 深入解析大语言模型的 Function Call 实现—— 以 Qwen2.5为例
  • 02-Canvas-fabric.ActiveSelection
  • 浅谈Mysql数据库事务操作 用mybatis操作mysql事务 再在Springboot中使用Spring事务控制mysql事务回滚
  • 数学 :矩阵