
在 go 的 goroutine 中直接使用 for 循环变量会导致所有协程共享同一个变量地址,造成输出结果异常(如全部打印 i=100),根本原因是闭包捕获的是变量引用而非值;正确做法是在每次迭代中创建局部副本。
在 go 的 goroutine 中直接使用 for 循环变量会导致所有协程共享同一个变量地址,造成输出结果异常(如全部打印 i=100),根本原因是闭包捕获的是变量引用而非值;正确做法是在每次迭代中创建局部副本。
在 Go 并发编程中,一个经典且极易踩坑的问题是:在 for 循环中启动多个 goroutine,并在匿名函数内直接引用循环变量(如 i)。初学者常误以为每个 goroutine 会“快照”当前的 i 值,但实际行为截然不同。
问题核心在于:Go 的匿名函数形成闭包时,捕获的是变量的内存地址(即引用),而非其当前值。由于 for 循环本身运行极快,而 goroutine 的调度存在延迟,当这些 goroutine 真正开始执行时,外层循环早已结束,此时循环变量 i 已稳定为终值(100)。因此,所有 goroutine 打印的 i 都是同一个最终值——导致输出类似:
0 100 1 100 2 100 ... 99 100
更危险的是,若未显式同步(如 fmt.Scanln() 阻塞主 goroutine),程序可能在部分 goroutine 执行前就退出,造成不可预测的竞态行为。
✅ 正确解法:在每次迭代中创建独立的局部变量副本,确保每个 goroutine 捕获的是专属值:
for i := 0; i <p>或等价地使用带参数的匿名函数(如原答案所示):</p><pre class="brush:php;toolbar:false;">for i := 0; i <p>⚠️ 注意事项:</p>
- 不要依赖 runtime.GOMAXPROCS 来“修复”此问题——它仅控制并行度,不改变变量捕获机制;
- Go 1.22+ 支持在 for range 循环中直接对迭代变量做 := 赋值实现自动副本(但传统 for init; cond; post 仍需手动处理);
- 若需传递多个变量,统一用局部副本或结构体封装,避免混合引用与值语义;
- 使用 go vet 或静态分析工具(如 staticcheck)可检测此类常见闭包陷阱。
总结:Go 的闭包按引用捕获变量是设计使然,不是 bug。理解这一机制,并养成在循环中显式创建副本的习惯,是编写健壮并发代码的关键基础。











