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

为何使用-race标志运行Go原子计数器代码时结果不符预期?

搞定Go原子计数器在-race模式下的异常问题

嘿,我刚看完你的问题,这情况其实是典型的WaitGroup使用不当加上竞态检测放大了问题导致的,咱们一步步拆解:

问题到底出在哪?

你说不用-race跑结果是对的,一加竞态检测就少了,核心原因不是原子操作本身(atomic.AddInt64绝对是线程安全的),而是你代码里WaitGroup的调用时机错了,再加上runtime.Gosched()推波助澜。

大概率你的代码是这么写的(从结果反推):

var wg sync.WaitGroup
counter := atomicCounter{}
for i := 0; i < 1000000; i++ {
    go func() {
        wg.Add(1)  // 错误:在goroutine内部调用wg.Add
        counter.Add(1)
        defer wg.Done()
    }()
}
wg.Wait()
fmt.Println(counter.Value())
  • 不开启-race时,goroutine调度没那么频繁,主goroutine循环启动1000000个goroutine的速度,赶不上大部分子goroutine执行wg.Add(1)的速度,所以wg.Wait()能等到所有子goroutine完成,结果正确。
  • 开启-race后,Go运行时会主动增加调度频率,主goroutine可能在很多子goroutine还没来得及执行wg.Add(1)的时候,就已经跑到wg.Wait()了。这时候WaitGroup的计数还是0,直接返回,主goroutine提前打印结果,而后面的子goroutine再执行Add也没用了。

另外你在Add方法里加的runtime.Gosched()会让子goroutine主动让出CPU,主goroutine更快到达wg.Wait(),直接加剧了这个问题。

怎么修复?

两步就能搞定:

  1. 把wg.Add移到goroutine外面:
    必须在启动goroutine之前就把WaitGroup的计数加上,保证主goroutine调用wg.Wait()时,计数已经是正确的1000000。比如:

    var wg sync.WaitGroup
    counter := atomicCounter{}
    total := 1000000
    wg.Add(total)  // 一次性加总计数,或者循环里每次加1都可以
    for i := 0; i < total; i++ {
        go func() {
            defer wg.Done()
            counter.Add(1)
        }()
    }
    wg.Wait()
    fmt.Println(counter.Value())
    
  2. 删掉没必要的runtime.Gosched():
    原子操作本身已经保证了线程安全,这里主动让出CPU完全没有意义,还会影响性能,直接删掉就行。

验证一下

修复后不管你加不加-race标志,运行结果都会稳定在1000000。其实竞态检测帮了你个大忙——它把你代码里隐藏的WaitGroup使用错误给揪出来了,不然这个bug可能在生产环境里偶尔出现,更难排查。

内容的提问来源于stack exchange,提问作者Roman

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.26 08:58:05