为何Go语言TestCal1与TestCal2两个测试用例输出结果不同?
为什么这两个Go测试用例会输出不同结果?
测试用例代码
TestCal1
func TestCal1(t *testing.T) { for i := 0; i < 10; i++ { go func() { time.Sleep(time.Second) fmt.Printf("%d ", i) }() } time.Sleep(2 * time.Second) }
TestCal2
func TestCal2(t *testing.T) { var i int for i = 0; i < 10; i++ { go func() { time.Sleep(time.Second) fmt.Printf("%d ", i) }() } time.Sleep(2 * time.Second) }
核心原因:循环变量的作用域与引用捕获
1. TestCal1的输出逻辑
在TestCal1里,循环变量i是每次迭代单独声明的(for i := 0; i <10; i++)。也就是说,循环跑10次,就会创建10个独立的i变量,每个迭代对应自己的i。
虽然协程要等1秒才执行打印,但每个协程绑定的是当前迭代那个i的引用。循环执行速度远快于协程延迟,等协程开始打印时,循环已经结束,但每个协程拿的是各自迭代时的i值。又因为Go runtime调度协程的顺序是随机的,所以输出是0到9的乱序组合。
2. TestCal2的输出逻辑
在TestCal2里,i是循环外提前声明的,整个循环全程用同一个i变量。
所有协程捕获的都是这个唯一的i的引用。循环会瞬间跑完,此时i已经自增到10(当i=9时执行i++变成10,不满足i<10的循环条件,循环终止)。1秒后协程开始执行,读取的都是已经变成10的i,所以全输出10。
额外补充:修复TestCal2的方法
如果想让TestCal2也输出0-9的乱序,可以给协程传递当前迭代的i值,避免引用捕获问题:
func TestCal2Fixed(t *testing.T) { var i int for i = 0; i < 10; i++ { go func(val int) { time.Sleep(time.Second) fmt.Printf("%d ", val) }(i) // 把当前i的值作为参数传入,协程拿到的是值的副本 } time.Sleep(2 * time.Second) }
内容的提问来源于stack exchange,提问作者elric wang
相关产品推荐
相关产品推荐

