所有 goroutine 最终都读到同一个变量的最终值;因 Go 闭包按引用捕获 for range 中唯一的 i/v 变量,显式传参或局部变量遮蔽(如 i := i)可解决。

for range 中直接用循环变量启动 goroutine 会出什么问题
所有 goroutine 最终都读到同一个变量的最终值,比如 i 总是打印 len(slice)-1,v 总是最后一次迭代的值。这不是 bug,是 Go 闭包按引用捕获变量的必然行为——i 和 v 在整个 for range 中只有一份内存地址,每次迭代只是改它的值。
显式传参是最安全、最推荐的写法
把当前迭代的值作为参数传给匿名函数,确保每个 goroutine 拿到的是独立副本,不会互相干扰。
-
go func(val int) { fmt.Println(val) }(i)—— 立即调用并传入当前i - 如果要传多个字段,建议打包成结构体或分别传参,比如
go func(idx int, item string) { ... }(i, v) - 注意:参数名和外部变量名可以不同,避免混淆;传参动作必须在
go后紧跟着(),不能写成go f(); f = func() {...}
在循环体内用 := 声明同名局部变量也有效
利用变量遮蔽(shadowing)机制,在每次迭代中创建新变量绑定当前值,效果等价于传参,但语义更隐晦些。
-
i := i这行代码不是冗余,它声明了一个新的局部i,生命周期仅限本轮迭代 - 后续
go func() { fmt.Println(i) }()捕获的是这个新变量,不是外层循环变量 - 对结构体或指针类型也适用,但要注意:如果原变量是
*T,v := v只复制指针,不深拷贝内容
容易忽略的关键点
即使用了 sync.WaitGroup 等待 goroutine 结束,也不能掩盖变量捕获错误;go vet 会报 range variable captured by func literal,但这个警告只覆盖部分场景——比如你用传统 for init; cond; post 形式且没做任何处理,它可能就静默放过。
立即学习“go语言免费学习笔记(深入)”;


















