先来看一段golang 1.22版本之前的for循环的代码
package main
import "fmt"
func main() {
done := make(chan bool)
values := []string{"chen", "hai", "feng"}
for _, v := range values {
fmt.Println("start")
go func() {
fmt.Println(v)
done <- true
}()
fmt.Println("end")
}
for _ = range values {
<-done
}
}
运行结果
输出的都是最后的"feng"
在for循环体里,匿名函数和循环变量v形成了闭包。循环变量v只会创建一次,每次迭代都会更新。而且这样的写法会导致for循环结束后才执行goroutine代码,这时候变量v里保存的是最后一个值,所以这里会输出"feng"。
以下提供两种常用的正确写法
第一种方法是在匿名函数中添加参数val,每个val都会被独立计算并保存到goroutine的栈中,所以可以达到预期的结果
package main
import "fmt"
func main() {
done := make(chan bool)
values := []string{"chen", "hai", "feng"}
for _, v := range values {
fmt.Println("start")
go func(val interface{}) {
fmt.Println(val)
done <- true
}(v)
fmt.Println("end")
}
for _ = range values {
<-done
}
}
此时的运行结果
第二种写法:在for循环体内定义新的变量。循环体内定义的变量在遍历的过程中是不共享的,所以可以达到期望的效果。
package main
import "fmt"
func main() {
done := make(chan bool)
values := []string{"chen", "hai", "feng"}
for _, v := range values {
fmt.Println("start")
val := v
go func() {
fmt.Println(val)
done <- true
}()
fmt.Println("end")
}
for _ = range values {
<-done
}
}
此时的运行结果
升级到最新版本1.22,同样的代码
package main
import "fmt"
func main() {
done := make(chan bool)
values := []string{"chen", "hai", "feng"}
for _, v := range values {
fmt.Println("start")
go func() {
fmt.Println(v)
done <- true
}()
fmt.Println("end")
}
for _ = range values {
<-done
}
}
现在运行结果如下
在golang 1.22中,循环的每次迭代都会创建新的变量,有效避免了以往版本中常见的闭包陷阱,提高了代码的安全性。
另外,1.22之前的版本,for range仅支持array, slice, string, map, channel等类型,现在新增了interger类型,这意味着我们可以像这样写代码
package main
import "fmt"
func main() {
for i := range 10 {
fmt.Println(i)
}
}