Loop variable captured by func literal
/这几天在学习Go语言,一段代码让我感到百思不得其解。
1  | package main  | 
输出:
1  | three  | 
从表面来看,上面的代码中每循环一次创建一个协程,来打印结构体中的name字段。第一次循环,v的值为{"one"},所以第一个创建的协程应该打印"one",第二个协程应该打印"two",以此类推。但多次运行该程序,输出结果始终只有"three"。这不太符合常理,于是我对代码进行了一些改动,在循环内部,为打印函数添加一个wrapper:
1  | for _, v := range data {  | 
输出结果还是相同,但此时编译器发出了警告:
1  | loop variable v captured by func literal  | 
这句警告是什么意思我也没有搞懂。我想用其他的办法找出这个bug的源头。添加--race选项运行该程序,得到的结果如下:
1  | WARNING: DATA RACE  | 
确实发生了数据竞争,但内存地址为0x00c00011a210处是哪一个变量无从知晓,猜测是v。但从数据竞争也无法从逻辑上推出程序的运行结果,作罢,求助于Google,找到了Stack Overflow上的一个帖子。
高赞给出的解释是这样的:在循环中创建的这些goroutine中,v指向for...range循环中的循环变量v,也就是说,在这些goroutine运行的时候,v的值是for...range循环中的v,而不是这些goroutine创建时v的值。线程main的运行速度很快,在这些goroutine还没有运行起来的时候,循环遍历已经完成,所以在这些goroutine运行时,协程中的v全部指向最后一个循环变量,在这个例子中就是{"three"}。如果想要达到不同的线程输出不同值的效果,就要用传参的方式来创建这些goroutine:
1  | for _, v := range data {  | 
此时程序的输出结果为:
1  | three  | 
而且多运行几次,每一次运行的结果都有可能不同。