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

【Go】C++ 转 Go 第(五)天:Goroutine 与 Channel | Go 并发编程基础

本专栏文章持续更新,新增内容使用蓝色表示。

食用指南

本文适合 C++ 基础的朋友,想要快速上手 Go 语言。可直接复制代码在 IDE 中查看,代码中包含详细注释和注意事项。

Go 的环境搭建请参考以下文章:

【Go】C++ 转 Go 第(一)天:环境搭建 Windows + VSCode 远程连接 Linux -CSDN博客

Go语言的并发模型建立在 Goroutine 和 channel 之上。

1. Goroutine

Goroutine 理论基础,此处留个空,后期补。

Goroutine 是本质上是一种由 Go 运行时(runtime)调度的轻量级线程。与传统线程相比,goroutine 的创建和销毁开销很小,其初始栈大小仅为 2KB,且支持动态扩容。程序可以同时运行多个 goroutine,它们共享相同的地址空间。

特性

  • 创建机制:通过 go 关键字快速创建,支持命名函数与匿名函数两种形式

  • 生命周期:主 goroutine 的退出将导致程序立即终止,所有子 goroutine 会被强制结束

  • 主动控制:通过 runtime.Goexit() 可实现当前 goroutine 的立即终止

  • 资源管理:结合 defer 语句确保 goroutine 退出时的资源清理与状态维护

  • 通信限制:goroutine 的函数返回值无法直接获取,必须依赖 channel 机制进行跨协程数据传递

goroutine.go

package mainimport ("fmt""runtime""time"
)// 从goroutine
func newTask() {for i := 0; ; i++ {fmt.Printf("new Goroutine: i= %d \n", i)time.Sleep(1 * time.Second)}
}// 主goroutine
// 主退出,从一同退出
func main() {// 创建一个go程去执行newTask()调度go newTask()// 无参匿名函数go func() {defer fmt.Println("匿名 A defer")// 外层想退出,可以直接returnfunc() {defer fmt.Println("匿名 B defer")// 内部这层使用return只会退到外层,使用以下函数可实现退出runtime.Goexit()fmt.Println("-----B-----")time.Sleep(2 * time.Second)}() // 如果没有结尾的()相当于只定义了,未调用fmt.Println("-----A-----")time.Sleep(2 * time.Second)}()// 有参匿名函数go func(a int, b int) bool {fmt.Println("a =", a, "b =", b)return true // 从协程和主协程是一个异步的操作// 如果想要主go程得到从go程的值,需要借助管道机制}(11, 12)// 开启这部分的死循环,就不会退出了for {time.Sleep(1 * time.Second)}// fmt.Println("main Goroutine exit.\n")
}

运行结果:

2. Channel

goroutine 之间的通信通过channel(通道)实现。通道提供了一种安全、同步的方式,用于在goroutine 之间传递数据。使用通道可以避免多个 goroutine 同时访问共享数据而导致竞态条件的问题,因为管道的读写是原子性的。

2.1 无缓冲 Channel 的同步通信

无缓冲 channel 实现了 goroutine 间的同步数据交换机制,确保发送和接收操作的严格时序一致性,为并发程序提供可靠的通信基础。

1)同步通信:发送操作会阻塞,直到有接收方准备好;接收操作会阻塞,直到有数据可接收。

2)数据消费:数据具有一次性消费特性,读取后数据从 channel 中移除。

3)死锁风险:发送和接收次数必须匹配,不匹配会导致 goroutine 永久阻塞。

1_channel.go

// channel 管道,两个goroutine之间的通信机制
// 一、无缓冲的channel
package mainimport ("fmt""time"
)func main() {// 无缓冲没有地方存放数据// 1. 定义无缓冲 channelc := make(chan int)go func() {defer fmt.Println("sub goroutine exited.")fmt.Println("sub goroutine is running......")// 2. 向channel中写入c <- 666c <- 555time.Sleep(1 * time.Second)subNum := <-cfmt.Println("subNum =", subNum)}()// 思考问题:// 1) main goroutine会不会比sub goroutine先执行导致无法接收到数据,无法打印出num?// 答:无缓冲channel的同步特性确保了数据传输的时序:发送者会等待接收者,接收者会等待数据。// 2) 管道中的数据读出来之后还会有吗?// 答:channel中的数据具有一次性消费特性,读取后即从channel中移除。// 读取次数和发送次数不等会导致goroutine永久阻塞,产生deadlock// 3. 从channel中读数据// <-和c之间不能有空格<-c        // 读取并丢弃num := <-c // 读取并赋值c <- 100fmt.Println("num =", num)fmt.Println("main goroutine exit.")}

运行结果:

2.2 带缓冲 Channel 的异步通信

带缓冲 channel 在同步通信基础上引入异步能力,通过固定大小的缓冲区调节生产者和消费者之间的速率差异,实现更灵活的并发控制。

异步通信:缓冲区未满时,发送操作不会阻塞;缓冲区不为空时,接收操作不会阻塞。

阻塞条件:缓冲区满时,发送操作阻塞;缓冲区空时,接收操作阻塞。

可以通过 len() 和 cap() 函数可实时获取缓冲区的当前使用情况和总容量

2_channel.go

// 二、带缓冲的channel
package mainimport ("fmt""time"
)func main() {// channel缓冲区满,写数据阻塞;channel缓冲区空,读数据阻塞// 1. 定义有缓冲 channelc := make(chan int, 2)fmt.Println("初始channel:len(c) =", len(c), "cap(c) =", cap(c))go func() {defer fmt.Println("sub goroutine exited.")fmt.Println("sub goroutine is running......")// 2. 向channel中写入for i := 0; i < 4; i++ {c <- ifmt.Println("sub向channel发送:", i, ",len(c) =", len(c), ",cap(c) =", cap(c))time.Sleep(50 * time.Millisecond)}}()// time.Sleep(1 * time.Second)for i := 0; i < 2; i++ {fmt.Println("main 接收到:", <-c, ",len(c) =", len(c), ",cap(c) =", cap(c))}time.Sleep(1 * time.Second)fmt.Println("main goroutine exited.")
}

运行结果:

2.3 Channel 的关闭与遍历

关闭操作:使用 close(c) 关闭 channel,关闭后不能再发送数据。

数据读取:已关闭的 channel 可以继续读取剩余数据,使用 data, ok := <-c 检测 channel 状态

range 遍历:自动检测 channel 关闭,简化数据读取代码。

注意:重复关闭 channel 或向已关闭 channel 发送数据都会引发运行时 panic。

3_closeChannel.go

// 使用close关闭channel
// 确定没有数据发送时,使用close关闭
package mainimport "fmt"func main() {// 定义一个channelc := make(chan int)go func() {defer fmt.Println("sub goroutine exited.")fmt.Println("sub goroutine is running......")for i := 0; i < 4; i++ {c <- i}// 关闭channelclose(c)// 如果没有close(c),会发生死锁,可以注释掉尝试一下// 如果向已关闭的channel发送数据会发生以下错误// panic: send on closed channel// c <- 99}()// for {// 	// 这种写法将作用域限制在if中// 	// 条件是ok为true// 	if data, ok := <-c; ok {// 		fmt.Println("接收到data:", data)// 	} else {// 		break// 	}// }// 以上代码可以优化为以下的写法for data := range c {fmt.Println("使用range接收到data:", data)}defer fmt.Println("main goroutine exited.")
}

运行结果:

2.4 Select 多路复用

select 语句允许在多个通道操作中选择一个执行。这种方式可以有效地处理多个通道的并发操作,避免了阻塞 。

多路监控:同时监听多个 channel 的读写状态,任一通道就绪即可触发相应操作。当多个 case 同时就绪时,随机选择一个执行,避免饥饿现象。

注意:所有 case 都未就绪时会阻塞,可以使用 default 实现非阻塞操作。

应用场景:超时控制(time.After创建一个定时器,在超时后执行特定的操作,避免永久阻塞。);多 channel 数据处理;goroutine 退出控制。

4_selectChannel.go 

// select 可以在同一流程下监控多个channel的读写状态
// 类似C/C++中的select、poll、epoll
package mainimport "fmt"func fibonacli(c, quit chan int) {x, y := 1, 1for {select {case c <- x:// 如果 c 可写,则满足此 case// 什么时候不满足,取决于 sub 读的次数x = yy = x + ycase <-quit:fmt.Println("fibonacli end.")return}}
}func main() {c := make(chan int)quit := make(chan int)// subgo func() {for i := 0; i < 6; i++ {fmt.Println(<-c)}quit <- 0}()// mainfibonacli(c, quit) // 引用语义
}

运行结果:


如有问题或建议,欢迎在评论区中留言~

http://www.dtcms.com/a/532589.html

相关文章:

  • 算法:283. 移动零
  • 设计微信公众号的网站吗举例说明seo
  • 欧米伽男士手表官方网站wordpress下载类型主题
  • Chrome离线版下载版,Chrome离线版安装文件,Chrome离线包
  • 上饶网站建设多少钱分销网站有哪些
  • 阿里云 Qwen 模型的 OpenAI SDK 调用
  • 什么是提示词(Prompt),提示词类型、结构解析
  • MES系列-制造流程数字化的实现
  • 我想在网站上卖食品怎么做百度知道网址
  • 对于使用队列实现栈以及用栈实现队列的题目的解析
  • Spring Boot3零基础教程,事件驱动开发,设计登录成功后增加积分记录信息功能,笔记61
  • 网站开发进度表网络电话免费版
  • 两种Redis序列化对比
  • 精确优化长尾关键词以提升SEO效果的战略分析
  • 分析对手网站wordpress制作功能型网站
  • Spring AOP注解配置实战:从概念到代码实现的深度解析(含核心关键词)
  • 【图像算法 - 31】基于深度学习的太阳能板缺陷检测系统:YOLOv12 + UI界面 + 数据集实现
  • 火山方舟 Responses API 实战指南:从概念到「公司尽调 Dossier 生成器」
  • 【推荐系统3】向量召回:i2i召回、u2i召回
  • 网站建设及系统开发wordpress仿微信菜单栏
  • 网站死链接怎么处理网页版浏览器怎么设置
  • 【仿RabbitMQ的发布订阅式消息队列】--- 介绍
  • Frobenius范数:矩阵分析的万能度量尺
  • 做网站 php asp.net jsp学院网站建设实例
  • [论文阅读] 从 5MB 到 1.6GB 数据:Java/Scala/Python 在 Spark 中的性能表现全解析
  • 算法--滑动窗口(一)
  • 新房网站建设公司永和建设集团有限公司网站
  • 【Rust编程:从新手到大师】Rust 环境搭建(详细版)
  • SQL188 每月及截止当月的答题情况
  • 珠海网站专业制作网站开发和运维区别