You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 19:55:07