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 |
而且多运行几次,每一次运行的结果都有可能不同。