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

sync.Cond多goroutine场景下死锁问题排查与优化

解决sync.Cond Broadcast导致的死锁问题及定位方法

死锁原因

当前代码的核心问题是Broadcast执行时机过早:

  • 启动三个subscribe goroutine后,main立刻调用Broadcast,但此时goroutine可能还没完成加锁、进入Wait状态,导致通知直接被“错过”。
  • 后续subscribe goroutine执行到Wait时,会一直等待通知,但不会再有新的Broadcast触发,三个goroutine永久阻塞;同时main里的wg.Wait()永远等不到三个wg.Done(),也陷入阻塞。最终所有goroutine都休眠,触发死锁。

优化后的代码

要确保所有subscribe goroutine都进入Wait状态后,再执行Broadcast。可以用额外的WaitGroup来同步准备状态:

package main

import (
    "fmt"
    "sync"
)

type Button struct {
    Clicked *sync.Cond
}

func subscribe(cond *sync.Cond, btnMessage string, wg *sync.WaitGroup, readyWG *sync.WaitGroup) {
    defer wg.Done()
    cond.L.Lock()
    // 告诉main:我已经加锁,准备好等待了
    readyWG.Done()
    cond.Wait()
    fmt.Println(btnMessage)
    cond.L.Unlock()
}

func test6() {
    var wg sync.WaitGroup
    var readyWG sync.WaitGroup

    button := Button{
        Clicked: sync.NewCond(&sync.Mutex{}),
    }

    wg.Add(3)
    readyWG.Add(3)
    go subscribe(button.Clicked, "Button 1", &wg, &readyWG)
    go subscribe(button.Clicked, "Button 2", &wg, &readyWG)
    go subscribe(button.Clicked, "Button 3", &wg, &readyWG)

    // 等待所有goroutine都准备好进入Wait状态
    readyWG.Wait()
    button.Clicked.Broadcast()

    wg.Wait()
}

func main() {
    test6()
}

优化点说明:

  • 新增readyWG用来同步subscribe的准备状态,每个goroutine加锁后先调用readyWG.Done(),告诉main自己已经准备好等待。
  • main先等待readyWG.Wait(),确认三个goroutine都进入等待状态后,再执行Broadcast,此时通知能被所有goroutine接收到。

死锁定位方法

  1. 分析Go自动输出的死栈信息
    Go运行时会自动检测全局死锁,打印所有goroutine的调用栈:

    • 从日志里能看到goroutine 1卡在sync.(*WaitGroup).Wait,说明main在等子goroutine结束;
    • goroutine 6-8卡在sync.(*Cond).Wait,说明这些子goroutine一直在等待条件通知。结合这两点就能推断出通知没被接收到,导致双向阻塞。
  2. 添加关键日志调试
    在加锁后、Wait前、Broadcast前后打印日志,观察执行顺序:

    // 在subscribe里加日志
    cond.L.Lock()
    fmt.Printf("%s: 已加锁,准备Wait\n", btnMessage)
    readyWG.Done()
    cond.Wait()
    // 在main里加日志
    readyWG.Wait()
    fmt.Println("所有goroutine准备完毕,执行Broadcast")
    button.Clicked.Broadcast()
    

    通过日志能直观看到Broadcast是否在所有Wait之前执行,快速定位时机问题。

  3. 使用pprof分析goroutine状态
    运行程序时开启pprof:go run -cpuprofile cpu.pprof main.go,然后用go tool pprof cpu.pprof进入交互模式,输入goroutine命令查看所有goroutine的阻塞状态,精准定位阻塞点。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 07:15:59