goroutine 中直接引用 for 循环变量会导致所有协程输出终值,因 Go 闭包按引用捕获变量且 i 在整个循环中只声明一次;for range 的 v、k 同理危险,需通过传参或局部变量遮蔽确保每个 goroutine 拥有独立副本。

goroutine 中直接引用 for 循环变量会输出终值
所有 goroutine 最终打印的都是循环结束后的变量值(比如 i 变成 6 后才开始执行),而不是各自迭代时的值。这不是 bug,是 Go 闭包按引用捕获变量的必然行为——i 在整个 for 块中只声明一次,内存地址不变,所有匿名函数读的都是同一块地址。
常见错误现象:
- 输出全是
6、40或"c",而非预期的1~5、0~39、"a", "b", "c" - 用
go vet会提示loop variable captured by func literal - 开启
-race检测时可能报 data race,尤其当变量被多个 goroutine 读写时
for range 中的 v 和 k 同样危险
for k, v := range slice 的迭代变量也复用内存地址。哪怕你没显式修改 v,只要 goroutine 执行滞后,它就读到最后一次赋值后的 v。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 不要在 goroutine 内直接用
v或k,哪怕只是fmt.Println(v) - 用
fmt.Printf("%p", &v)能验证所有迭代共享同一地址 - 对 map 迭代更要小心:
v是值拷贝,但若v是指针或结构体字段含指针,仍可能引发意外共享
两种安全写法:传参 or 局部遮蔽
核心原则是让每个 goroutine 拿到独立副本,而非共享引用。
方案一(推荐):把变量作为参数传给匿名函数
for i := 1; i <= 5; i++ {
wg.Add(1)
go func(i int) { // ✅ 显式接收参数,完成值拷贝
fmt.Println(i)
wg.Done()
}(i) // ✅ 立即传入当前 i 的值
}
方案二:在循环体内用同名变量遮蔽(shadowing)
for i := 1; i <= 5; i++ {
i := i // ✅ 创建新变量,与外层 i 同名但独立
wg.Add(1)
go func() {
fmt.Println(i) // 此 i 是局部副本
wg.Done()
}()
}
注意:go func() { ... }(i) 的括号必须紧贴函数字面量,不能换行或加空格,否则语法错误。
WaitGroup 使用时机容易错
wg.Add(1) 必须在 goroutine 启动前调用,且不能放在 goroutine 内部——否则可能因调度延迟导致计数未增加就进入 wg.Wait(),提前返回。
常见错误:
- 把
wg.Add(1)写在go func() { ... }()里面 → 计数漏加 - 用
defer wg.Done()但没配defer所在函数的生命周期(比如 goroutine 函数里没defer或位置不对)→Done()不执行或重复执行 - 没等
wg.Wait()就退出 main,goroutine 被强制终止
真正容易被忽略的是:哪怕你修复了变量捕获问题,wg 用错照样 panic 或静默失败。并发逻辑里,变量作用域和同步原语必须一起校准。


















