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

固定数量worker协程模式下WaitGroup调用竞态条件问题咨询

问题根因确认

你的判断完全正确,该竞态确实是**子goroutine执行wg.Add的同时主goroutine调用wg.Wait**导致的。
sync.WaitGroup的官方设计要求明确规定:所有wg.Add调用必须在对应的wg.Wait调用触发前完成,两者并发执行会直接触发内部计数器的读写竞态,符合你拿到的竞态检测报告特征。

你当前代码的执行时序漏洞非常典型:主goroutine往channel发送完9个任务后立刻调用wg.Wait,此时部分任务可能还没有被worker goroutine消费到,对应的wg.Add还没执行,WaitGroup内部计数器可能还是0,wg.Wait会直接返回,甚至出现任务未执行完就关闭channel的问题。

常见误区解答

wg.Add并非必须全部在主goroutine中调用,核心约束只有一个:对应同一个Wait阶段的wg.Add操作,必须全部在wg.Wait触发前完成。

动态任务场景的解决方案

针对任务数量不固定、后续持续流入的场景,最常用的改造方案是把wg.Add调用移动到任务发送侧,每次发送任务前先执行wg.Add(1),不需要提前预知任务总数,发多少就计数多少,只要你确认所有任务都已发送完成后再调用wg.Wait即可。

修改后可运行的无竞态代码示例

func Test(t *testing.T) {
    t.Run("", func(t *testing.T) {
        var wg sync.WaitGroup
        queuedTaskC := make(chan func())
        // 启动固定数量worker
        for i := 0; i < 5; i++ {
            wID := i + 1
            go func(workerID int) {
                // 消费到channel关闭自动退出
                for task := range queuedTaskC {
                    task()
                }
            }(wID)
        }

        taskFn := func() {
            fmt.Println("executing task...")
            wg.Done()
        }
        // 每次发送任务前先Add,保证Add操作全部在Wait前完成
        for i := 0; i < 9; i++ {
            wg.Add(1)
            queuedTaskC <- taskFn
        }

        // 所有任务发送完成后再等待执行完毕
        wg.Wait()
        close(queuedTaskC)

        fmt.Println(len(queuedTaskC))
    })
}

扩展场景适配

如果你的任务是由其他goroutine异步发送的,只需要保证所有发送任务的goroutine都遵守「发任务前先wg.Add(1)」的规则,等你收到所有任务都已发送完成的信号后,再调用wg.Wait即可正常运行。
如果需要同时等待worker goroutine全部退出,可以再新增一个独立的WaitGroup专门统计worker的生命周期,关闭任务channel后等待所有worker退出即可。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.04 20:06:02