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

复用WaitGroup引发死锁:是否需创建新的WaitGroup?

嘿,我来帮你拆解下这个死锁问题的根源,以及对应的解决办法~

问题原因分析

首先得明确一个关键点:sync.WaitGroup是设计用来一次性等待一组goroutine完成的,绝对不能在调用过Wait()之后复用它。

回到你的代码里,第一个阶段你用wg来等待所有func1的goroutine完成,并且启动了一个goroutine执行wg.Wait(),当所有func1都调用wg.Done()后,wg的计数归0,这个Wait()就返回了,同时关闭了ch。

但接下来你又把同一个wg拿来给func2的goroutine用:遍历ch的时候每次调用wg.Add(1),然后启动func2,再启动另一个goroutine执行wg.Wait()来等待func2完成。

这里的问题在于,WaitGroup内部有一个状态标记,当Wait()返回后,它的内部状态就进入了“等待完成”的终态,后续再调用Add()和Done()都不会正确更新计数,也不会唤醒后续的Wait()调用。这就导致第二个wg.Wait()会永远阻塞,而负责关闭ch2的goroutine卡在这里,主goroutine又在等着遍历ch2,最终整个程序所有goroutine都进入休眠状态,触发死锁错误。

而你用新的wg2就能正常运行,是因为wg2是全新的,没有被调用过Wait(),它的计数从0开始,Add()、Done()和Wait()的流程都是完全符合预期的,不会有状态混乱的问题。

解决方法

最直接也最安全的解决方式就是给不同阶段的并发任务使用独立的WaitGroup,就像你注释掉的wg2那样。这里给你整理好可以直接运行的修正代码:

package main

import (
	"fmt"
	"sync"
	"time"
)

func main() {
	wg1 := sync.WaitGroup{}
	ch := make(chan int)
	for a := 0; a < 3; a++ {
		wg1.Add(1)
		go func1(int(3-a), ch, &wg1)
	}
	go func() {
		wg1.Wait()
		close(ch)
	}()

	wg2 := sync.WaitGroup{}
	ch2 := make(chan string)
	for val := range ch {
		fmt.Println(val)
		wg2.Add(1)
		go func2(val, ch2, &wg2)
	}
	go func() {
		wg2.Wait()
		close(ch2)
	}()

	for val := range ch2 {
		fmt.Println(val)
	}
}

func func1(seconds int, ch chan<- int, wg *sync.WaitGroup) {
	defer wg.Done()
	time.Sleep(time.Duration(seconds) * time.Second)
	ch <- seconds
}

func func2(seconds int, ch chan<- string, wg *sync.WaitGroup) {
	defer wg.Done()
	ch <- "hello"
}

另外再补充一个小知识点:WaitGroup的官方文档明确标注了WaitGroup must not be copied after first use,而且复用调用过Wait()的WaitGroup属于未定义行为,所以在实际开发中,一定要让每个WaitGroup只负责一组相关的goroutine等待任务,用完就丢弃,不要复用。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.11 09:25:43