for range的闭包问题

在之前,Go中的for range语句中用短声明形式定义的循环变量是整个循环共享同一个,这就导致在for range语句中的闭包在循环结束后引用的该循环变量最后都会变为同一个值。

1
2
3
4
5
6
7
8
9
10
11
func main() {
var wg sync.WaitGroup
for i := 0; i < 3; i++ {
wg.Add(1)
go func() {
defer wg.Done()
fmt.Println(i)
}()
}
wg.Wait()
}

例如在上面的代码中,最后输出的结果为:

1
2
3
3
3
3

这是因为闭包实际上捕获的是对变量 i 的引用,在闭包函数运行时,循环语句已经结束,i 的值已经变为了3,所以输出的结果自然都是3

如果要实现预期输出,则需要使每个闭包函数的引用各不相同,修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
func main() {
var wg sync.WaitGroup
for i := 0; i < 3; i++ {
wg.Add(1)
x:=1
go func() {
defer wg.Done()
fmt.Println(x)
}()
}
wg.Wait()
}

每次通过新定义变量x 来实现不同闭包函数对不同变量的引用,就能得到预期输出(由于是并发执行,输出结果不一定有序):

1
2
3
0
1
2

Go 1.22版本的编译器已经对此做了优化,现在循环变量的定义变为每次迭代定义一个,所以每次闭包函数捕获的引用值会不同,现在直接使用变量i 也能够得到预期输出。

参考资料

https://tonybai.com/2024/02/18/some-changes-in-go-1-22/

https://cloud.tencent.com/developer/article/2395693

https://linux.do/t/topic/219504