在 go 中,匿名函数(闭包)可以安全引用外部作用域的变量,但需注意变量捕获方式为“按引用共享”而非“按值复制”,因此修改外部变量会影响闭包行为,尤其在 goroutine 中易引发竞态或意外结果。
在 go 中,匿名函数(闭包)可以安全引用外部作用域的变量,但需注意变量捕获方式为“按引用共享”而非“按值复制”,因此修改外部变量会影响闭包行为,尤其在 goroutine 中易引发竞态或意外结果。
Go 的闭包会捕获变量的引用(即内存地址),而非变量在定义时刻的值。这意味着:只要闭包访问的是同一变量名,它看到的就是该变量当前的最新值——无论是在主 goroutine 还是其他 goroutine 中。
例如,以下代码看似无害,实则存在典型陷阱:
func main() {
for i := 0; i < 3; i++ {
go func() {
fmt.Println(i) // 全部输出 3!
}()
}
time.Sleep(time.Millisecond * 10) // 确保 goroutines 执行完成
}原因在于:i 是循环中复用的单一变量,所有闭包共享对 i 的引用;当循环结束时,i 值为 3,所有 goroutine 打印的都是最终值。
✅ 正确做法:通过参数传入当前值,实现“快照式”捕获:
func main() {
for i := 0; i < 3; i++ {
go func(val int) {
fmt.Println(val) // 输出 0, 1, 2(顺序不定)
}(i) // 立即传入当前 i 的值
}
time.Sleep(time.Millisecond * 10)
}⚠️ 特别注意:
- 若变量是局部指针(如 &x),闭包持有其地址,后续修改原值将被所有闭包感知;
- defer 语句中的闭包同样遵循此规则(见示例中 err 被重赋值后,defer 仍打印 "error 2");
- 在并发场景下,若多个 goroutine 同时读写同一被捕获变量,必须加锁或使用通道同步,否则触发数据竞争(可用 go run -race 检测)。
总结:引用变量本身没有错误,但必须理解 Go 闭包的变量共享语义。避免在循环或并发上下文中直接捕获可变变量;优先采用显式参数传递、或在 goroutine 内部声明独立副本(如 i := i)来隔离状态。这是写出健壮、可预测 Go 并发代码的关键基础。


















