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

GO协程(Goroutine)问题总结(待续)

在使用Go语言来编写代码时,遇到的一些问题总结一下
[参考文档]:https://www.topgoer.com/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/goroutine.html

1. main()函数默认的Goroutine

场景再现:今天在看到这个教程的时候,在自己的电脑上跑了一下示例的代码。
发现了描述与我的执行结果不同的地方,官方文档描述如下:

这一次的执行结果只打印了main goroutine done!,并没有打印Hello Goroutine!。

但是我执行后的情况是,如图:
 main()函数默认的Goroutine

可以看到,我最终的执行结果是都输出了,而不是只输出了
main goroutine done!
Why?
原因是——虽然 main() 函数中调用了 go hello(),主 goroutine 在打印完 main goroutine done! 后就会退出,但:

在主 goroutine 退出前,如果新启动的 goroutine 有足够的时间运行完,Hello Goroutine! 就会输出。
上面这段代码启动了一个新 goroutine,但程序的执行是并发的,不是同步/阻塞的。

执行流程是:

go hello() 启动了一个新 goroutine;

fmt.Println(“main goroutine done!”) 被执行;

如果此时 main() 返回前,新 goroutine 还没来得及执行完,那它也会被强行终止;

但如果它已经执行完了,就能看到打印的内容。

这两句都成功输出,是因为你的电脑配置比较好,执行速度非常快,新启动的 goroutine 来得及在 main() 退出前完成打印。

正确做法:用 sync.WaitGroup 或 time.Sleep

2 . Go 协程(Goroutine)的两个关键点

协程不能保证执行的顺序,但是如果加了time.sleep的话,可以保障协程执行完毕

✅ Go 协程(Goroutine)的两个关键点:
①. 协程是并发的,不能保证执行顺序
go hello() 启动后,什么时候运行是由 Go 调度器决定的。

主协程和子协程是“谁抢到 CPU 谁先跑”,谁先打印是不确定的。

所以:

go hello()
fmt.Println("main done")

有可能先打印 main done,也可能先打印 Hello,取决于当时调度情况。

② 加 time.Sleep() 可以“间接保障”子协程执行完
加 time.Sleep() 相当于强行让主协程等一下,给子协程留时间执行完。

所以子协程通常会有时间执行完,看起来“像是被保障了执行”。
❗但注意:time.Sleep() ≠ 可靠同步
虽然 time.Sleep() 很简单,但它存在几个问题:

问题点说明
❌ 不精准你不知道子协程到底需要多少时间,sleep 多了浪费,少了又执行不完
❌ 不可扩展如果你有多个协程,就很难 sleep 到合适的时间
✅ 适合临时调试用于演示或实验是可以的

✅ 正确做法:用 sync.WaitGroup


import ("fmt""sync"
)func hello(wg *sync.WaitGroup) {fmt.Println("Hello Goroutine!")wg.Done() // 协程结束,通知 WaitGroup
}func main() {var wg sync.WaitGroupwg.Add(1)        // 告诉 WaitGroup 等待 1 个协程go hello(&wg)    // 启动协程wg.Wait()        // 等待所有协程结束fmt.Println("main goroutine done!")
}

这样就能准确地等待协程执行完再退出,不用靠 sleep。

总结一句话:
time.Sleep() 是简单粗暴的等待方式,可以在小程序中“凑合用”,但真正写程序,用 sync.WaitGroup 等同步机制更稳、更准、更专业。

3.defer

3.1 defer使用对比
func hello(i int) {defer wg.Done() // goroutine结束就登记-1fmt.Println("Hello Goroutine!", i)
}func hello(i int){fmt.Println("Hello Goroutine!",i)wg.Done()
}
//在 正常情况下的效果是一样的:都会确保在 goroutine 执行完成后调用 wg.Done(),
//从而通知 WaitGroup,减少一个等待计数。

✅ defer wg.Done() 的优势:
defer 会在 函数返回前自动执行,即使函数中间发生了 panic(未被恢复),defer 也会运行(前提是没有让程序直接崩溃)。

这意味着:

func hello(i int) {defer wg.Done()// 如果这里出现错误,也能保证 Done 会执行fmt.Println("Hello Goroutine!", i)
}

更安全、稳健,防止遗漏。

❗ 手动调用 wg.Done() 有风险:

func hello(i int) {fmt.Println("Hello Goroutine!", i)wg.Done()
}

如果你写了更复杂的逻辑,中途 return 或 panic 了,wg.Done() 可能根本执行不到,就会导致 wg.Wait() 永远卡住。
✅ 总结:

写法是否推荐原因
defer wg.Done()✅ 推荐更安全,即使中途出错也能保证 Done 被调用
手动调用 wg.Done()⚠️ 慎用必须确保函数末尾一定能执行到,否则容易漏调用

所以你的判断是对的:“两种写法是一样的”,功能上是对的;但为了避免未来的问题,推荐使用 defer 写法,更健壮、易维护。

3.2defer的作用、在Goroutine中的使用案例以及执行顺序
3.2.1 defer的作用

✅ defer 的作用
defer 用于延迟一个函数的执行,直到外围函数(即当前函数)返回之前才调用。

换句话说:

无论当前函数中发生了什么(正常结束或提前 return),defer 注册的语句都会在函数结束前自动执行。

📌 举个例子说明:

func demo() {fmt.Println("start")defer fmt.Println("this is defer")fmt.Println("end")
}

输出结果是:

start
end
this is defer

defer fmt.Println(“this is defer”) 被延迟执行到 demo() 函数退出前的最后一刻。

✅ defer 的常见用途

用途示例说明
释放资源defer file.Close()防止文件忘记关闭
解锁defer mu.Unlock()防止死锁
记录日志/退出操作defer log.Println("退出")确保函数末尾执行
Done 通知defer wg.Done()保证 goroutine 退出时减少计数
3.2.2 在Goroutine中的使用案例
package mainimport ("fmt""sync"
)var wg sync.WaitGroupfunc hello(i int) {defer wg.Done() // 必须在协程结束时调用 wg.Done() 通知 WaitGroup 协程结束fmt.Println("Hello Goroutine!", i)
}
func main() {for i := 0; i < 10; i++ {wg.Add(1)   // 告诉 WaitGroup 等待 1 个协程go hello(i) // 启动另外一个goroutine去执行hello函数}wg.Wait() // 等待所有的 goroutine 结束fmt.Println("main goroutine done!")
}
3.2.3 多个defer的执行顺序

defer 在 Go 中的执行顺序就是栈结构的“先进后出(LIFO)”原则:

🧠 defer 的执行顺序:后注册,先执行
也就是:

func demo() {defer fmt.Println("1")defer fmt.Println("2")defer fmt.Println("3")
}

🧾 输出结果:

3
2
1

📦 原理解释:
当你写多个 defer 时,Go 会把它们压入一个栈中,函数结束时就从栈顶往下依次执行这些 defer。

行号执行内容进入 defer 栈
1defer fmt.Println("1")栈:[1]
2defer fmt.Println("2")栈:[1, 2]
3defer fmt.Println("3")栈:[1, 2, 3]
return前执行 defer从栈顶开始弹出输出顺序:3 → 2 → 1

✅ 常见用途
多个 defer 通常用于按顺序清理资源,比如:

func handleFile() {f1, _ := os.Open("file1.txt")defer f1.Close() // 最后关闭f2, _ := os.Open("file2.txt")defer f2.Close() // 比 f1 先关闭fmt.Println("doing something...")
}

结果是 file2 会先关闭,然后 file1 再关闭 —— 和你打开文件的顺序是相反的,这正是我们想要的行为(像“关门要倒着来”一样)。

✅ 总结:

特性说明
顺序多个 defer 是“后注册,先执行”(LIFO)
应用场景清理资源(文件、锁、连接等)、错误处理等
安全性即使函数 returnpanic,也会执行

4.主协程和其他协程的关系,主协退出了,其他的协程还执行吗?

代码使用了官网提供的:

package mainimport ("fmt""time"
)func main() {// 合起来写go func() {i := 0for {i++fmt.Printf("new goroutine: i = %d\n", i)time.Sleep(time.Second)}}()i := 0for {i++fmt.Printf("main goroutine: i = %d\n", i)time.Sleep(time.Second)if i == 2 {break}}
}

执行结果:

main goroutine: i = 1
new goroutine: i = 1
main goroutine: i = 2
new goroutine: i = 2
new goroutine: i = 3Process finished with the exit code 0

证明了主协程结束,其他线程不会再执行

5. java/c/c++线程与go协程的对比(与OS线程)

特性Java / C 的线程(OS Thread)Go 的 goroutine
线程类型操作系统线程(内核线程)用户级线程(协程)
线程模型1:1 模型M:N 模型
调度者操作系统Go 自带的调度器(runtime)
映射关系每个语言线程对应一个 OS 线程多个 goroutine 映射到多个 OS 线程
栈内存初始大小通常 1MB~2MB(固定)起始约 2KB(可动态伸缩)
创建成本高(需要系统调用)极低(用户态,几乎无开销)
调度成本高(内核态线程切换)低(用户态线程切换)
并发数量限制一般几千个十万甚至百万级
适合场景计算密集、高性能场景高并发、大量 I/O 场景
常用语言APIstd::thread, Threadgo myFunc()
内存使用效率相对较低非常高

🔍 示例类比:

类比Java / C 的线程Go 的 goroutine
比喻重型卡车:开销大但能干活自行车大军:轻量且灵活
调度员操作系统Go 自己的调度器
数量几千个已很吃力十万个都轻轻松松

✅ 图示说明

Java / C         =>        1:1 线程模型
┌──────────┐         ┌──────────┐
│ Thread A │───────▶│  OS 线程 A │
│ Thread B │───────▶│  OS 线程 B │
└──────────┘         └──────────┘Go              =>        M:N 线程模型
┌──────────────┐
│ goroutine 1  │
│ goroutine 2  │
│ goroutine 3  │──┐
│ goroutine 4  │  │
│ goroutine 5  │  ├──▶ 被 Go runtime 调度
│ goroutine N  │──┘     分配到 OS 线程 A/B/C…
└──────────────┘

✅ 总结一句话:
Java 和 C 的线程就是系统线程(1:1),重量级。
Go 的 goroutine 是用户级线程,轻量可扩展(M:N),适合高并发。

相关文章:

  • openvino如何在c++中调用pytorch训练的模型
  • [蓝桥杯]摆动序列
  • 深入解析光敏传感技术:嵌入式仿真平台如何重塑电子工程教学
  • Linux驱动:register_chrdev_region、 alloc_chrdev_region
  • 让回归模型不再被异常值“带跑偏“,MSE和Cauchy损失函数在噪声数据环境下的实战对比
  • Python Day44
  • 智慧园区数字孪生全链交付方案:降本增效30%,多案例实践驱动全周期交付
  • SQL进阶之旅 Day 16:特定数据库引擎高级特性
  • 华为OD最新机试真题-小明减肥-OD统一考试(B卷)
  • 华为OD最新机试真题-数组组成的最小数字-OD统一考试(B卷)
  • Python训练营---Day44
  • 今日科技热点速览
  • Android协程学习
  • 消息的幂等性
  • RAID磁盘阵列
  • Kafka存储机制核心优势剖析
  • 作为过来人,浅谈一下高考、考研、读博
  • 26考研 | 王道 | 计算机组成原理 | 四、指令系统
  • 如何搭建自动化测试框架?
  • 【leetcode】347. 前k个高频元素
  • 有哪些做政府网站的相关公司/网络推广公司收费标准
  • 外贸商做英文网站的目的/百度网络营销中心app
  • 徐州圣道网络科技有限公司/泉州网站建设优化
  • 自己做盗版小说网站吗/百度网页版首页
  • php网站建设案例教程视频教程/阿里云搜索引擎入口
  • 网站设计中下拉列表怎么做/小广告清理