for循环中直接用goroutine引用i会输出全部终值:因闭包捕获变量i的地址,所有goroutine共享同一i,循环结束时i为终值,导致全部输出终值。

for循环中直接用goroutine引用i会输出全部终值
这是最常踩的坑:写for i := 0; i ,结果全打印<code>5。不是i没变,而是所有匿名函数共享同一个i变量地址;等goroutine真正执行时,循环早结束了,i只剩终值。
根本原因不是Go“bug”,而是闭包按引用捕获——它拿的是内存地址,不是快照值。
- 哪怕只启一个goroutine,只要它延迟执行,就可能读到终值
-
go vet会报loop variable i captured by func literal,但默认不启用,建议加进CI - 开启
-race能复现数据竞争警告,但不能预防逻辑错误
range循环里用job变量传给goroutine仍会出错
写for _, job := range jobs { go func() { process(job) }() },结果所有goroutine都处理最后一个job。因为job是range每次迭代复用的变量名,地址始终不变。
和传统for循环本质一样,只是语法糖掩盖了变量复用事实。
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
- 打印
&job会发现地址恒定,而你期望每个goroutine绑定不同地址 - 不要试图用
&job再解引用——那还是指向同一块内存 - 结构体值拷贝开销通常可忽略,别过早优化
两种可靠写法:局部重声明 or 参数传值
二者都有效,选哪个看团队习惯和可读性偏好,但必须二选一,不能省略。
-
局部重声明(推荐):
for i := 0; i —— 这行<code>i := i不是废话,它创建新绑定,地址独立 -
参数传值:
for i := 0; i —— 函数参数天然隔离,语义更直白 - 别混用:比如
go func() { ... }(i)是立即调用,返回值类型不对,编译失败
defer里捕获循环变量不需要特殊处理
defer里的匿名函数和goroutine行为完全不同:它同步执行、按栈逆序、无调度延迟。写for i := 0; i 会输出<code>210,符合预期。
这不是因为defer“聪明”,而是它不涉及并发调度时机错位——所有defer都在当前goroutine结束前跑完。
- 但若defer里再启goroutine,那个goroutine仍要按前述规则处理循环变量
- 别因为defer安全,就误以为所有闭包场景都安全
time.AfterFunc、http.HandleFunc、cron任务回调,甚至测试中的test.Cleanup,只要变量生命周期超出当前作用域,就得显式隔离。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










