本文详解 go 并行处理数组时因闭包捕获循环变量 i 引发的竞态问题,揭示为何所有 goroutine 输出相同结果,并提供传参式闭包、for-range 改写等安全、高效的并行实现方案。
本文详解 go 并行处理数组时因闭包捕获循环变量 i 引发的竞态问题,揭示为何所有 goroutine 输出相同结果,并提供传参式闭包、for-range 改写等安全、高效的并行实现方案。
在 Go 中使用 goroutine 并行处理切片(如计算两数组对应元素的相关性)时,一个经典且易被忽视的陷阱是:循环变量在闭包中被意外共享。你遇到的输出全为 4 + 50 = 54,并非并发逻辑错误,而是典型的“循环变量逃逸”问题。
根本原因在于:Go 的 for 循环中,变量 i 是单个可复用的内存地址,所有匿名函数(goroutine)都引用了这个同一变量。当主 goroutine 快速执行完循环(i 最终变为 4),而子 goroutine 尚未开始或刚启动时,它们读取的 i 值已是循环终止后的最终值 —— 因此全部访问 a1[4] 和 a2[4],导致重复结果。
✅ 正确解法:为每个 goroutine 显式传递当前迭代值的副本,切断对循环变量的引用:
for i := 1; i <p>⚠️ 注意事项:</p>
- 不要依赖 wg.Wait() 在循环体内强制同步(如原代码注释所示),这会退化为串行执行,完全丧失并行意义;
- runtime.GOMAXPROCS(8) 在现代 Go(1.5+)中通常无需手动设置,运行时已默认启用多 OS 线程;
- 若需更高安全性与可读性,推荐使用 for range 配合索引显式捕获:
for i := range a1[1:4] { // 切片表达式更清晰地限定范围
idx := i + 1 // 还原原始索引
wg.Add(1)
go func(j int) {
defer wg.Done()
fmt.Printf("%d + %d = %d\n", a1[j], a2[j], a1[j]+a2[j])
}(idx)
}
? 总结:Go 的闭包捕获的是变量的引用而非值。在并行循环中,务必通过函数参数、局部变量赋值(val := i)等方式,确保每个 goroutine 持有独立的数据快照。这是编写健壮、可预测并发 Go 程序的基础原则。











