导航菜单

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

搜索