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

为何wg.Done()放置位置不同会触发sync.WaitGroup重用报错

问题原因解析

首先明确两个核心规则:

  • sync.WaitGroup官方使用规范明确要求:当Wait()方法已经被调用后,在该Wait()返回前,不允许再调用Add()方法将计数器从0改为正数值,否则就会抛出WaitGroup is reused before previous Wait has returned错误。
  • Go语言defer语句执行规则:只有defer语句本身被执行到时,对应的延迟函数才会被注册到函数返回栈;如果代码在走到defer语句前就返回,该延迟函数不会执行。

第一种写法(defer wg.Done()放在worker开头)报错原因

你的代码中wg是全局变量,同时有1000个callWorker goroutine并发操作同一个WaitGroup,每个callWorker的执行逻辑为:打印日志→调用wg.Add(1)→启动worker goroutine→调用wg.Wait()。
当defer wg.Done()放在worker开头时,worker函数只要被调用,第一行就会把wg.Done()注册为延迟函数,不管后续走哪个分支返回,wg.Done()一定会执行。
此时极易出现如下触发错误的时序:

  1. goroutine A执行wg.Add(1),wg计数器变为1
  2. goroutine A启动worker goroutine,worker很快执行完逻辑,调用wg.Done(),wg计数器回到0
  3. 此时goroutine B刚好执行到wg.Add(1),把wg计数器从0改为1
  4. 但goroutine A此时还处于wg.Wait()调用过程中,还未返回,遇到计数器从0被改回正数值的情况,直接触发预设报错。

第二种写法未报错的本质

你的worker函数中存在恒成立的if true { return nil }逻辑,当你把defer wg.Done()放在该if分支之后,代码走到if就直接返回,永远不会执行到defer wg.Done()这一行,因此wg.Done()从来不会被调用,wg的计数器只会持续增加,永远不会降到0。
所有callWorker中的wg.Wait()都会永久阻塞,不会出现Wait()调用过程中计数器从0被改回正数的场景,自然不会抛出对应错误。
注意:第二种写法本身是错误用法,并非“正常运行”,只是没有触发你看到的报错而已,实际上所有worker的Done通知都没有发出,Wait永远不会返回,会造成goroutine泄露。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.04 04:30:01