Go 协程 goroutine 初探:新手极易踩坑的并发陷阱
前言
Go 语言最亮眼的特性之一就是原生支持并发,依靠 goroutine 实现轻量级任务调度。刚接触协程时很容易产生错觉:仅仅加上 go 关键字就能实现并发,但实际编码里会遇到不少出人意料的问题。本篇结合入门典型案例,聊聊 goroutine 基础规则与经典闭包陷阱。
1. 什么是 goroutine
goroutine 是 Go 运行时管理的轻量级执行单元,相比操作系统线程,它初始栈内存仅有 2KB,支持动态伸缩,能够轻松创建上万并发任务,创建、调度开销远小于 OS 线程。 启动方式极其简单,在函数调用前增加 go 关键字: go func (){ fmt.Println ("执行协程任务") }()
2. 新手第一个大坑:主协程退出,所有子协程直接终止
很多初学者会写出下面这段代码: package main
import "fmt"
func main () { for i := 0; i < 3; i++ { go func () { fmt.Println (i) }() } } 运行之后大概率没有任何输出。 核心原理: main 函数本身就是主 goroutine。一旦 main 函数执行完毕,整个进程会直接退出,运行时不会等待任何正在调度、还未执行完成的子 goroutine。子协程甚至还没抢到 CPU 时间片执行代码,就随着进程一同销毁。
想要等待协程执行完成,不能依赖 time.Sleep 这种不稳定方案,工程中标准做法是使用 sync.WaitGroup 或者 channel 实现同步等待。
3. 第二个经典陷阱:闭包循环变量捕获引用
就算我们加上等待逻辑,上面代码依旧存在问题:最终打印的数字大概率全是 3,而不是预期的 0、1、2。 原因:闭包捕获循环变量 i 时,捕获的是变量内存地址,并不是循环每一轮的数值快照。 for 循环执行速度远快于 goroutine 调度,当协程真正执行打印逻辑时,循环早已结束,变量 i 的值固定为 3。
两种标准修复方案
方案 1:将循环变量作为参数传入匿名函数,发生值拷贝 for i := 0; i < 3; i++ { go func (val int) { fmt.Println (val) }(i) }
方案 2:循环内部新建局部变量,开辟独立内存 for i := 0; i < 3; i++ { val := i go func () { fmt.Println (val) }() }
4. 简单总结
- main 为主协程,main 退出,进程销毁,子 goroutine 不会等待;
- 循环内 goroutine 搭配匿名闭包,警惕变量引用捕获问题;
- goroutine 调度顺序不受代码书写顺序控制,输出顺序随机;
- 简单 Sleep 仅适合本地测试,正式代码使用同步原语等待协程。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)