range 循环陷阱
🟡 中等题目描述
以下代码的输出是什么?为什么?
func main() {
slices := [][]int{
{1, 2, 3},
{4, 5, 6},
{7, 8, 9},
}
for _, slice := range slices {
go func() {
fmt.Println(slice)
}()
}
time.Sleep(time.Second)
}期望输出
[7 8 9]
[7 8 9]
[7 8 9]进阶问题
以下代码的输出是什么?
func main() {
slices := [][]int{
{1, 2, 3},
{4, 5, 6},
{7, 8, 9},
}
for i, slice := range slices {
go func() {
fmt.Printf("i=%d, slice=%v\n", i, slice)
}()
}
time.Sleep(time.Second)
}期望输出
i=2, slice=[7 8 9]
i=2, slice=[7 8 9]
i=2, slice=[7 8 9]提示
- range 循环中的迭代变量是复用的
- 闭包捕获的是变量的引用,而不是值的拷贝
- 所有 goroutine 可能会在循环结束后才执行
解法
参考答案 (3 个标签)
range 闭包 变量捕获
核心问题
range 循环中的迭代变量在每次迭代中被复用,而不是创建新变量。
// 等价于
for i, slice := range slices {
// slice 是同一个变量,只是值在变化
go func() {
fmt.Println(slice) // 捕获的是变量引用
}()
}
// 循环结束后,slice = [7 8 9](最后一个值)
// 所有 goroutine 输出相同的值修复方法
方法 1:传递参数(推荐)
for _, slice := range slices {
go func(s []int) {
fmt.Println(s) // s 是参数,每次不同
}(slice) // 立即传参
}方法 2:创建局部变量
for _, slice := range slices {
slice := slice // 创建新变量(Go 1.22+ 语法)
go func() {
fmt.Println(slice)
}()
}方法 3:使用索引
for i := range slices {
go func(idx int) {
fmt.Println(slices[idx])
}(i)
}原理解析
// range 的底层实现
for i, slice := range slices {
// 实际上类似于:
// var i int
// var slice []int
// for len(slices) > 0 {
// i, slice = 0, slices[0] // 复用变量
// slices = slices[1:]
// }
go func() {
fmt.Println(slice) // 闭包捕获 slice 变量
}()
}
// 时间线:
// t0: slice = [1, 2, 3], goroutine 1 创建(未执行)
// t1: slice = [4, 5, 6], goroutine 2 创建(未执行)
// t2: slice = [7, 8, 9], goroutine 3 创建(未执行)
// t3: 循环结束,slice = [7, 8, 9]
// t4: goroutine 1, 2, 3 执行,都输出 [7, 8, 9]扩展:其他 range 陷阱
陷阱 1:range 指针
func main() {
users := []User{
{Name: "Alice"},
{Name: "Bob"},
{Name: "Charlie"},
}
var result []*User
for _, user := range users {
result = append(result, &user) // ❌ 都指向同一个变量
}
for _, u := range result {
fmt.Println(u.Name) // Charlie, Charlie, Charlie
}
}
// ✅ 修复
for i := range users {
result = append(result, &users[i]) // 使用索引
}陷阱 2:range Map
func main() {
m := map[string]int{
"a": 1,
"b": 2,
"c": 3,
}
for k, v := range m {
go func() {
fmt.Printf("k=%s, v=%d\n", k, v)
}()
}
time.Sleep(time.Second)
}
// 输出不确定,可能是 (c,3), (c,3), (c,3) 或其他组合陷阱 3:range Channel
func main() {
ch := make(chan int, 3)
ch <- 1
ch <- 2
ch <- 3
close(ch)
for v := range ch {
go func() {
fmt.Println(v)
}()
}
time.Sleep(time.Second)
}
// 输出:3, 3, 3