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。
实操建议:
- 不要在 goroutine 内直接用
v或k,哪怕只是fmt.Println(v) - 用
fmt.Printf("%p", &v)能验证所有迭代共享同一地址 - 对 map 迭代更要小心:
v是值拷贝,但若v是指针或结构体字段含指针,仍可能引发意外共享
两种安全写法:传参 or 局部遮蔽
核心原则是让每个 goroutine 拿到独立副本,而非共享引用。
方案一(推荐):把变量作为参数传给匿名函数
for i := 1; i
<p>方案二:在循环体内用同名变量遮蔽(shadowing)</p>
<pre class="brush:php;toolbar:false;">for i := 1; i
<p>注意:<code>go func() { ... }(i)</code> 的括号必须紧贴函数字面量,不能换行或加空格,否则语法错误。</p>
<h3>WaitGroup 使用时机容易错</h3>
<p><code>wg.Add(1)</code> 必须在 goroutine 启动前调用,且不能放在 goroutine 内部——否则可能因调度延迟导致计数未增加就进入 <code>wg.Wait()</code>,提前返回。</p>
<p>常见错误:</p>
- 把
wg.Add(1)写在go func() { ... }()里面 → 计数漏加 - 用
defer wg.Done()但没配defer所在函数的生命周期(比如 goroutine 函数里没defer或位置不对)→Done()不执行或重复执行 - 没等
wg.Wait()就退出 main,goroutine 被强制终止
真正容易被忽略的是:哪怕你修复了变量捕获问题,wg 用错照样 panic 或静默失败。并发逻辑里,变量作用域和同步原语必须一起校准。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











