首页 > 编程语言 >第七章:并发编程 1.Goroutines --Go 语言轻松入门

第七章:并发编程 1.Goroutines --Go 语言轻松入门

时间:2024-11-30 23:34:12浏览次数:8  
标签:WaitGroup -- Goroutine 堆栈 Go Goroutines 工作者

Go 语言中的 Goroutines 是一种轻量级的线程,它允许你以非常低的成本并发执行多个函数或方法。Goroutines 是 Go 并发模型的核心组成部分,与 channels 一起使用可以实现高效的并发编程。

什么是 Goroutines?

1.内存占用小

初始堆栈大小:每个 Goroutine 的初始堆栈大小非常小,通常是 2KB(Go 1.4 及以后版本)。相比之下,一个典型的线程可能需要几十 KB 到几 MB 的堆栈空间。
动态调整:Goroutine 的堆栈是动态调整的。当 Goroutine 需要更多堆栈空间时,Go 运行时会自动增加堆栈大小;当 Goroutine 结束时,堆栈空间会被回收。

2.创建和切换成本低

创建成本:创建一个新的 Goroutine 的成本非常低,通常只需要几个微秒。这使得你可以轻松地创建成千上万个 Goroutines。
上下文切换:Goroutine 的上下文切换由 Go 运行时管理,而不是操作系统。这意味着上下文切换的成本更低,因为不需要涉及内核态和用户态之间的切换。

3.多路复用到少量操作系统线程

调度器:Go 运行时有一个自己的调度器,它将多个 Goroutines 多路复用到少量的操作系统线程上。这样可以减少线程的开销,并且更高效地利用 CPU 资源。
并发模型:通过多路复用,Go 可以在单个操作系统线程上运行多个 Goroutines,从而实现高效的并发执行。

4.无阻塞 I/O

非阻塞 I/O:Go 的标准库提供了许多非阻塞 I/O 操作,这些操作可以与 Goroutines 结合使用,从而避免了传统的线程阻塞问题。例如,net/http 包中的 HTTP 请求处理就是基于 Goroutines 和非阻塞 I/O 的。
具体示例

如何创建 Goroutines

要创建一个 Goroutine,只需在函数调用前加上 go 关键字。例如:

package main

import (
	"fmt"
	"time"
)

// say 函数接收一个字符串参数 s,并在接下来的500毫秒内,每100毫秒打印一次该字符串。
// 该函数主要用于在一段时间内重复输出某个消息,用于示例演示。
func say(s string) {
	for i := 0; i < 5; i++ {
		time.Sleep(100 * time.Millisecond) // 暂停100毫秒以模拟延迟
		fmt.Println(s)                     // 打印传入的字符串参数
	}
}

func main() {
	go say("world") // 在一个新的Goroutine中调用say函数,传入参数"world"
	say("hello")    // 在当前Goroutine中调用say函数,传入参数"hello"
}

在这里插入图片描述

在这个例子中,say("world") 在一个新的 Goroutine 中运行,而 say("hello") 在主 Goroutine 中运行。由于主 Goroutine 和新创建的 Goroutine 同时运行,所以你会看到 “hello” 和 “world” 交替打印。

Goroutines 的生命周期

在 Go 语言中,Goroutines 的生命周期管理是并发编程的一个重要方面。理解 Goroutines 的生命周期可以帮助你更好地设计和调试并发程序。以下是 Goroutines 生命周期的详细解释:

1. 创建

  • 启动:使用 go 关键字可以启动一个新的 Goroutine。例如:

    go someFunction()
    

    这会立即在一个新的 Goroutine 中异步执行 someFunction

  • 初始堆栈大小:每个新创建的 Goroutine 都有一个初始堆栈大小(通常是 2KB),这个堆栈大小可以根据需要动态增长或收缩。

2. 执行

  • 并发执行:一旦 Goroutine 被启动,它就会开始并发执行。多个 Goroutines 可以并行运行,具体取决于可用的 CPU 核心数和 Go 运行时调度器的决策。

  • 调度:Go 运行时有一个自己的调度器,它负责将 Goroutines 分配到操作系统线程上。调度器会根据 Goroutines 的状态和优先级进行调度。

  • 上下文切换:当一个 Goroutine 需要等待 I/O 操作或其他阻塞操作时,调度器会将其挂起,并调度其他可运行的 Goroutines。这种上下文切换非常高效,因为它是用户态的,不需要涉及内核态的切换。

3. 结束

  • 函数返回:当 Goroutine 中的函数返回时,该 Goroutine 就结束了。如果函数返回了一个错误,你可以通过错误处理机制来捕获和处理这些错误。

  • 显式结束:有时你可能需要显式地结束一个 Goroutine。虽然 Go 语言本身没有提供直接终止 Goroutine 的方法,但可以通过一些技巧来实现这一点,例如使用 context 包中的 Context 来传递取消信号。

4. 等待所有 Goroutines 完成

  • 同步点:主 Goroutine 可能需要等待所有子 Goroutines 完成后再继续执行。为此,可以使用 sync.WaitGroup 或者 channel 来实现同步。

    • 使用 sync.WaitGroup

      package main
      
      import (
      	"fmt"
      	"sync"
      	"time"
      )
      
      // worker 函数接收两个参数:
      // - id: 工作者的唯一标识符
      // - wg: 一个 sync.WaitGroup 指针,用于等待所有工作者完成
      // 该函数模拟了一个工作者的行为,在启动和完成时分别打印消息,并在中间暂停100毫秒以模拟工作时间。
      func worker(id int, wg *sync.WaitGroup) {
      	defer wg.Done()                        // 完成后通知 WaitGroup
      	fmt.Printf("Worker %d starting\n", id) // 打印工作者启动消息
      	time.Sleep(100 * time.Millisecond)     // 暂停100毫秒以模拟工作时间
      	fmt.Printf("Worker %d done\n", id)     // 打印工作者完成消息
      }
      
      func main() {
      	var wg sync.WaitGroup // 初始化一个 WaitGroup
      	numWorkers := 5       // 设置工作者的数量
      
      	for i := 0; i < numWorkers; i++ {
      		wg.Add(1)         // 增加 WaitGroup 的计数
      		go worker(i, &wg) // 在一个新的 Goroutine 中启动工作者
      	}
      
      	wg.Wait()                            // 等待所有工作者完成
      	fmt.Println("All workers are done.") // 打印所有工作者完成的消息
      }
      
      

      在这里插入图片描述

    • 使用 channel

      package main
      
      import (
      	"fmt"
      	"time"
      )
      
      // worker 函数接收两个参数:
      // - id: 工作者的唯一标识符
      // - ch: 一个整型通道,用于传递完成信号
      // 该函数模拟了一个工作者的行为,在启动和完成时分别打印消息,并在中间暂停100毫秒以模拟工作时间。
      // 完成后将工作者的ID发送到通道 ch 中。
      func worker(id int, ch chan int) {
      	fmt.Printf("Worker %d starting\n", id) // 打印工作者启动消息
      	time.Sleep(100 * time.Millisecond)     // 暂停100毫秒以模拟工作时间
      	fmt.Printf("Worker %d done\n", id)     // 打印工作者完成消息
      	ch <- id                               // 将工作者的ID发送到通道 ch 中
      }
      
      func main() {
      	ch := make(chan int, 5) // 创建一个缓冲通道,容量为5
      
      	for i := 0; i < 5; i++ {
      		go worker(i, ch) // 在一个新的 Goroutine 中启动工作者
      	}
      
      	for i := 0; i < 5; i++ {
      		<-ch // 从通道 ch 中接收完成信号
      	}
      
      	fmt.Println("All workers are done.") // 打印所有工作者完成的消息
      }
      
      

在这里插入图片描述

5. 资源回收

  • 垃圾回收:当一个 Goroutine 结束时,其占用的资源(如堆栈空间)会被自动回收。Go 的垃圾回收机制会处理这些资源的释放,确保内存不会泄漏。

6. 错误处理

  • 错误处理:Goroutines 应该适当地处理错误,并且最好能够通知调用者。可以使用 error 类型、panicrecover 或者 channel 来传递错误信息。

总结

Goroutines 的生命周期包括创建、执行、结束和资源回收。通过合理的设计和使用 sync.WaitGroupchannel,可以有效地管理 Goroutines 的生命周期,确保程序的正确性和性能。理解和掌握这些概念对于编写高效的并发程序至关重要。

标签:WaitGroup,--,Goroutine,堆栈,Go,Goroutines,工作者
From: https://blog.csdn.net/weixin_42478311/article/details/144163030

相关文章

  • 1DCNN-2DResNet并行故障诊断模型
    往期精彩内容:Python-凯斯西储大学(CWRU)轴承数据解读与分类处理Pytorch-LSTM轴承故障一维信号分类(一)-CSDN博客Pytorch-CNN轴承故障一维信号分类(二)-CSDN博客Pytorch-Transformer轴承故障一维信号分类(三)-CSDN博客三十多个开源数据集|故障诊断再也不用担心数据集了!P......
  • 全网最低价 | 全家桶持续更新!
    往期精彩内容:时序预测:LSTM、ARIMA、Holt-Winters、SARIMA模型的分析与比较全是干货|数据集、学习资料、建模资源分享!EMD变体分解效果最好算法——CEEMDAN(五)-CSDN博客拒绝信息泄露!VMD滚动分解+Informer-BiLSTM并行预测模型-CSDN博客风速预测(一)数据集介绍和预处理_风......
  • 独家原创 | 超强组合预测模型!
    往期精彩内容:时序预测:LSTM、ARIMA、Holt-Winters、SARIMA模型的分析与比较全是干货|数据集、学习资料、建模资源分享!EMD变体分解效果最好算法——CEEMDAN(五)-CSDN博客拒绝信息泄露!VMD滚动分解+Informer-BiLSTM并行预测模型-CSDN博客风速预测(一)数据集介绍和预处理_风......
  • 大语言模型(1)--LLaMA
    LLaMA(LargeLanguageModelMetaAI)是由MetaAI于2023年2月发布的大语言系列模型,它应该是近两年来影响力最大的自然语言处理大模型。在它的带动下,雨后春笋般地涌现出来不同语言、不同领域下的各种大模型。值得注意的是,最早Meta在非商业许可的情况下发布了LLaMA的模型权重,仅......
  • C# mvc +vue+ axios+ api + javascript
    一整天,分享了几条随笔,C#mvc+axios+webapi+javascripthttps://www.cnblogs.com/insus/p/18577591asp.netmvc视图传递数据至另一页的视图https://www.cnblogs.com/insus/p/18578261C#mvc+angular+$http+webapi+javascripthttps://www.cnblogs.com/insus/p/1857......
  • [高等数学]一元积分学的应用
    平面图形的面积直角坐标系y=f(x)......
  • (SAST检测规则-1)Android - 权限管理漏洞
    所属分类:Android-权限管理漏洞缺陷详解:应用未正确实施最小权限原则或滥用已声明的权限可能导致敏感信息泄露。例如,恶意代码利用已授予的权限绕过用户授权,访问通讯录、位置、短信等敏感资源。部分开发者还可能滥用权限以执行不必要的操作,违反用户隐私或安全性。导致结果和风险......
  • 【实战】Oracle基础之重做日志文件(REDO)的6种dump方法
    关于Jady:★工作经验:近20年IT技术服务经验,熟悉业务又深耕技术,为业务加持左能进行IT技术规划,右能处理综合性故障与疑难杂症;★成长历程:网络运维、主机/存储运维、程序/数据库开发、大数据运维、数据库运维、数据管理;★擅长技术:Oracle/MySQL/PGSQL/SQLServer/ClickHouse/Elastic......
  • js 中 file 文件 应用
    文章目录文件上传File对象基本属性文件上传大文件上传文件格式校验通过type属性校验图片格式通过文件名扩展名校验文件解析一、处理图片文件流(以`Blob`格式接收文件流为例)二、处理文本文件流三、处理PDF文件流(借助PDF.js库来展示,需先引入PDF.js相关脚本)bas......
  • Linux操作系统2-进程控制3(进程替换,exec相关函数和系统调用)
    上篇文章:Linux操作系统2-进程控制2(进程等待,waitpid系统调用,阻塞与非阻塞等待)-CSDN博客本篇代码Gitee仓库:Linux操作系统-进程的程序替换学习·d0f7bb4·橘子真甜/linux学习-Gitee.com本篇重点:进程替换目录一.什么是进程替换?二.进程替换函数常用的函数 2.1......