
在go语言中,for循环变量被多个goroutine闭包共享时,因变量复用导致所有闭包最终都打印最后一次迭代的值;解决方法是将循环变量作为参数显式传入匿名函数,实现值的正确捕获。
在go语言中,for循环变量被多个goroutine闭包共享时,因变量复用导致所有闭包最终都打印最后一次迭代的值;解决方法是将循环变量作为参数显式传入匿名函数,实现值的正确捕获。
这是一个在Go并发编程中极为典型且容易踩坑的问题:循环变量在闭包中被捕获时,实际捕获的是变量的地址(即同一内存位置),而非每次迭代的瞬时值。
在你的示例代码中:
func main() {
lans := [5]string{"java", "python", "erlang", "cpp", "go"}
fin := make(chan bool)
for _, l := range lans {
go func() {
fmt.Println(l) // ❌ 所有 goroutine 共享同一个变量 l
}()
}
<-fin // 防止主协程退出
}虽然 for range 每次迭代看似为 l 赋予了新值,但 Go 编译器会复用栈上的同一变量 l(即 l 的内存地址不变)。当所有 goroutine 启动后,for 循环早已结束,l 的最终值为 "go"。因此,所有并发执行的匿名函数读取的都是这个最终值 —— 输出全是 "go"。
✅ 正确做法:通过函数参数将当前迭代的值“快照”传递进去,利用参数的值拷贝语义确保每个 goroutine 拥有独立副本:
立即学习“go语言免费学习笔记(深入)”;
func main() {
lans := [5]string{"java", "python", "erlang", "cpp", "go"}
fin := make(chan bool, 5) // 建议缓冲通道避免阻塞
for _, l := range lans {
go func(lang string) { // ✅ 显式接收参数,形成独立绑定
fmt.Println(lang)
}(l) // ✅ 立即传入当前 l 的值(按值传递)
}
// 等待全部打印完成(简易同步,生产环境建议用 sync.WaitGroup)
for i := 0; i < 5; i++ {
<-fin
}
}⚠️ 注意事项:
- 不要依赖
time.Sleep做同步,它不可靠且掩盖并发逻辑缺陷; - 若需等待多个 goroutine,优先使用
sync.WaitGroup替代 channel 计数; - Go 1.22+ 中
for range的循环变量行为未改变,该陷阱依然存在; - 对于结构体或大对象,传参虽是值拷贝,但若性能敏感,可传指针并确保无数据竞争。
总结:闭包捕获的是变量本身,而非其某次迭代的值;要捕获“当前值”,必须通过函数参数、局部变量赋值(如 v := l; go func(){...}())等方式显式创建副本。 这一原则适用于所有基于共享变量的并发闭包场景。


















