Go语言WaitGroup疑问:中途调用Done()及defer死锁解析
关于sync.WaitGroup的两个疑问解答
问题1:goroutine中途调用wg.Done()的原因
我理解wg.Done()用于向WaitGroup告知goroutine已完成,使wg.Wait()结束,通常会用defer确保在goroutine末尾调用。但遇到如下代码在goroutine中途调用wg.Done(),且后续代码仍在执行,对此感到困惑,想了解这样做的原因:
func startNetworkDaemon() *sync.WaitGroup { var wg sync.WaitGroup wg.Add(1) go func() { connPool := warmServiceConnCache() server, err := net.Listen("tcp", "localhost:8080") if err != nil { log.Fatalf("cannot listen: %v", err) } defer server.Close() wg.Done() for { conn, err := server.Accept() if err != nil { log.Printf("cannot accept connection: %v", err) continue } svcConn := connPool.Get() fmt.Fprintln(conn, "") connPool.Put(svcConn) conn.Close() } }() return &wg }
解答
这段代码里的WaitGroup核心作用是等待网络服务初始化完成,而非等待整个goroutine生命周期结束:
warmServiceConnCache()预热连接池、net.Listen()启动TCP监听,这两步是服务启动的关键初始化操作,必须确保完成后,外部调用startNetworkDaemon()返回的WaitGroup的wg.Wait()才能结束,以此告知调用方:服务已经准备好接受客户端请求了。- 后续的
for循环是服务持续处理连接的主逻辑,会一直运行到服务关闭。如果把wg.Done()放在goroutine末尾,wg.Wait()永远无法等到结束信号(因为循环不会主动退出),也就无法向外部传达服务就绪的状态。所以提前在初始化完成后调用wg.Done(),既能让外部感知服务就绪,又不影响goroutine继续处理后续请求。
问题2:给wg.Done()添加defer触发死锁的原因
另一段代码中,若给wg.Done()添加defer会触发死锁,想请解释该死锁的原因:
func usingBroadcast() { beeper := sync.NewCond(&sync.Mutex{}) var wg sync.WaitGroup wg.Add(1) miniFunc(func() { fmt.Println("mini1") wg.Done() }, beeper) beeper.Broadcast() wg.Wait() } func miniFunc(fn func(), beeper *sync.Cond) { var wg sync.WaitGroup wg.Add(1) go func() { wg.Done() beeper.L.Lock() fmt.Println("i am waiting") beeper.Wait() beeper.L.Unlock() fn() }() wg.Wait() }
解答
先看修改后触发死锁的代码(将wg.Done()改为defer wg.Done()):
func miniFunc(fn func(), beeper *sync.Cond) { var wg sync.WaitGroup wg.Add(1) go func() { defer wg.Done() // 此处修改为defer beeper.L.Lock() fmt.Println("i am waiting") beeper.Wait() beeper.L.Unlock() fn() }() wg.Wait() }
死锁的连锁逻辑如下:
miniFunc启动goroutine后,goroutine先执行beeper.L.Lock()获取条件变量的锁,接着调用beeper.Wait()——这个方法会自动释放锁并阻塞,等待外部的Broadcast信号。但此时defer wg.Done()还未执行,因为goroutine还没有退出。miniFunc中的wg.Wait()会一直阻塞,等待goroutine调用wg.Done(),但goroutine卡在beeper.Wait()处,没有机会执行到defer wg.Done()。- 回到
usingBroadcast()函数,miniFunc()因内部的wg.Wait()阻塞,导致后续的beeper.Broadcast()根本无法执行;没有广播信号,goroutine就会一直卡在beeper.Wait(),miniFunc持续阻塞,usingBroadcast里的wg.Wait()也永远等不到fn()中的wg.Done(),最终整个程序陷入死锁。
而原代码中,goroutine先调用wg.Done(),再执行加锁等待逻辑,miniFunc的wg.Wait()能正常结束,usingBroadcast()里的beeper.Broadcast()得以执行,唤醒等待的goroutine后,fn()才会被调用,整个流程无阻塞。
内容的提问来源于stack exchange,提问作者ferraro
相关产品推荐
相关产品推荐

