不再共享循环变量
在老版本的 Go 中,for 循环迭代器的变量是一个单一变量,在每个循环迭代中仅是取值不同。这样做在性能上非常高效,但如果使用不当,会导致意想不到的行为,可能会造成共享循环变量的问题。
最经典的场景就是在 goroutine 循环时的问题。如下代码:
func main() { values := []int{1, 2, 3, 4, 5} for _, val := range values { go func() { fmt.Printf("%d ", val) }() } time.Sleep(time.Second * 3) }
输出结果:
5 5 5 5 5
如果是 Go1.22 以前的版本,在不做任何变更的情况下。我们需要把代码改成如下:
func main() { values := []int{1, 2, 3, 4, 5} for _, val := range values { go func(val int) { fmt.Printf("%d ", val) }(val) } time.Sleep(time.Second * 3) }
其他场景下,也会需要写 v := v
的代码来再次赋值。经典的很。
但在 Go1.22 起,不再需要这么干了。之前文章《Go 团队将修改 for 循环变量的语义,Go1.21 新版本即可体验!》提到的 GOEXPERIMENT=loopvar
特性已经默认加到该版本。语法将默认改变。
也就是在 Go 1.22 中,for 循环的每次迭代都会创建新变量,每次循环迭代各自的变量,以避免意外共享错误。上面一模一样的代码,输出结果不再是固定的 5。
而是非固定、非稳定有序的值,例如:2 4 3 5 1 等随机结果。
如下输出:
// 运行第一次 2 3 1 4 5 // 运行第二次 2 3 1 5 4 // 运行第三次 5 1 2 3 4 ...
很多面试官喜欢拿这个来做面试题。敲黑板了,请大家改一下面试题和答案了。
支持循环整数类型
在 for 循环中,对整数类型的范围进行循环迭代。代码如下:
func main() { for i := range 7 { fmt.Println("煎鱼", i) } fmt.Println("煎鱼进脑子了!") }
在老版本的 Go 中,会直接报如下错误:
$ go run demo.go # command-line-arguments ./demo.go:8:17: cannot range over 7 (untyped int constant)
但在 Go1.22 起,支持了该功能特性。
输出结果:
煎鱼 0 煎鱼 1 煎鱼 2 煎鱼 3 煎鱼 4 煎鱼 5 煎鱼 6 煎鱼进脑子了!
算是补全一个 for 循环的小缺漏。
总结
在 Go1.22 起,对 for 循环的共享变量进行了一次整治,新版本将会采用新的语法进行。
如果对你的老代码升级有影响,可以主动配置 GOEXPERIMENT=loopvar
来保持逻辑一致。
本次的整型类型范围的 for 循环支持,也算是填补和拓展了程序里的用法。
最后的最后,Go 面试题中有用到上述特性的同学,请注意修改问题和答案。免得没跟上新版本的迭代。