Go 中 for 循环变量复用同一内存地址,不会每次迭代新建变量;例如 for i := 0; i < n; i++ 中的 i 始终是同一个变量。

for 循环变量在 Go 中是复用的,不是每次迭代新建
Go 的 for 语句不会为每次迭代分配新变量,而是反复使用同一块栈内存。比如 for i := 0; i 中的 <code>i 始终是同一个地址;每次循环只是给这个地址写入新值。当你在循环体里写 go func() { fmt.Println(i) }(),所有匿名函数捕获的都是这个地址——而不是某次迭代时的值。
协程启动是异步的,主 goroutine 很快跑完循环(微秒级),i 已变成 5;等那些 goroutine 真正调度执行时,读到的自然全是 5。这不是“延迟”或“调度慢”,是确定性的内存复用行为。
闭包捕获的是变量地址,不是值快照
Go 的匿名函数闭包对循环变量的引用是**按引用捕获**,这点和 JavaScript、Python 等语言一致。它不自动做深拷贝或快照,也不感知“当前迭代”。只要变量没逃逸出作用域,它就一直指向那个物理地址。
- 错误写法:
go func() { use(i) }()→ 所有 goroutine 共享&i - 正确本质:必须让每个 goroutine 持有独立的值副本,而非共享引用
两种安全写法的区别与适用场景
方案一:参数传值(推荐)go func(val int) { use(val) }(i)
优点:语义清晰,参数名可自解释,IDE 和静态分析工具能更好识别意图;适合需要多个变量或复杂逻辑的场景。
方案二:循环内重声明(简洁但易误读)i := i 再 go func() { use(i) }()
本质是创建同名局部变量,触发值拷贝。注意:这行必须放在 go 之前,且不能和外层 i 混淆作用域;多人协作时容易被当成冗余代码删掉。
两者都避免了数据竞争,但参数方式更利于后期扩展(比如加日志、加 context、加 error 处理)。
不手动拷贝可能触发 -race 检测器报错
当循环变量是结构体字段或指针(如 for _, m := range members { go func() { db.Exec(..., m.ID) }() }),问题会升级为真实的数据竞争:m.ID 在循环中被反复写,而 goroutine 并发读——go run -race 会直接报 Data race on field ...。
即使变量是基础类型(int、string),不拷贝也会导致逻辑错误,只是不一定会被 -race 捕获(因为无写冲突,只有读错值)。所以不能依赖检测器,得从编码习惯上杜绝。
最常被忽略的一点:range 循环里的 v 同样复用内存,哪怕你只用 v.Name,只要没拷贝,就可能读到下一轮覆盖后的脏值。


















