go并发之捕获迭代变量
对于go语言来说,下面这个问题可谓是非常经典了。下面代码会打印出什么结果?
func main() {
for i := 0; i < 10; i++ {
go func() {
fmt.Println(i)
}()
}
}
// 这是我的结果 7 7 7 7 7 7 10 10
通过结果我们看到。第一:打印的数字不足10个,这个问题我们不在这里讨论。第二:打印出来的数字并不是0~9,而是有重复的,今天我们主要关注这个问题,大家可能都知道这种情况,今天我们来好好聊聊为什么会在这样。
要解释上述问题,第一我们要了解go变量的作用域:go的变量作用域在一个个块中(block),一个花括号就是一个块,比如函数、if语句,for语句都有花括号,甚至直接写一个花括号就可以验证。下面代码是会有两个问题:1.变量a定义了却没有使用。2.fmt.Println找不到变量a
func main() { { var a int = 1 } fmt.Println(a) }
第二我们要了解go中的闭包,在最初的问题中我们就是用func匿名函数实现了一个闭包,而且这个闭包函数直接使用了闭包所在的作用域中的变量i。而这个i的内存地址和for循环中的i是同一个内存地址,随着迭代的进行变量i的内存地址里面存放的数组也在发生了变化,导致打印出来的数值不是期望的0~9,可以通过捕获迭代变量的方法来达到我们预期的效果,上代码:
func main() { for i := 0; i < 10; i++ { a := i go func() { fmt.Println(a) }() } }
这里我们用了一个变量a来存放变量i中的数值,每次迭代时a变量会重新申请内存地址用来存放i的值,这样每个闭包中的a在内存中就是各自独立的变量,不会出现打印相同值了。
当然还有一种处理方式,将i以变量的方式传入函数,也与变量的作用域有关,每个a值都是内存地址独立的变量:
func main() { for i := 0; i < 10; i++ { go func(a int) { fmt.Println(a) }(i) } }