在循环中使用匿名函数捕获循环变量会出现问题:

    1. func main() {
    2. var funcs []func()
    3. for i := 0;i < 3; i++ {
    4. funcs = append(funcs, func() {
    5. fmt.Printf("%d, ", i)
    6. })
    7. }
    8. for _, f := range funcs {
    9. f()
    10. }
    11. }
    12. // 3, 3, 3,

    修改为一下形式就不会出现问题:

    1. func main() {
    2. var funcs []func()
    3. for i := 0;i < 3; i++ {
    4. ti := i // 赋值给另一个变量
    5. funcs = append(funcs, func() {
    6. fmt.Printf("%d, ", ti)
    7. })
    8. }
    9. for _, f := range funcs {
    10. f()
    11. }
    12. }
    13. // 0, 1, 2,

    原因在于匿名函数捕获的是变量的地址,而该地址处的值在循环中多次改变。修改后的版本将其赋值给另一个变量,捕获到的地址不一样。

    1. func main() {
    2. funcs := make([]func(), 0)
    3. for i := 0; i < 3; i++ {
    4. ti := i
    5. funcs = append(funcs, func() {
    6. fmt.Printf("%v, %v\n", &i, &ti)
    7. })
    8. }
    9. for _, f := range funcs {
    10. f()
    11. }
    12. }
    13. //0x1400001e088, 0x1400001e090
    14. //0x1400001e088, 0x1400001e098
    15. //0x1400001e088, 0x1400001e0a0

    以上问题同样会出现在基于匿名函数的goroutine中。

    1. func main() {
    2. const t = 3
    3. ch := make(chan struct{})
    4. for i := 0; i < t; i++ {
    5. go func() {
    6. fmt.Printf("%d, ", i)
    7. ch <- struct{}{}
    8. }()
    9. }
    10. for i := 0; i < t; i++ {
    11. <-ch
    12. }
    13. }
    14. // 3, 3, 3,

    在这里顺带提一句,go中的局部变量可能在堆上分配,也可能在栈上分配,由编译器实现,不保证分配在哪。堆变量的gc由运行时完成。