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

为何Go语言示例中互斥区内的条件变量不会引发死锁?

关于Go语言条件变量避免死锁的疑问

我在O'Reilly的一项培训中遇到如下Go语言示例,该示例通过条件变量防止widgetInventory变为负数。示例可正常运行,但我无法理解当makeSales获取mutex且widgetInventory为0时,程序为何不会死锁——我原本预期此时会发生死锁。

var (
    wg sync.WaitGroup
    mutex = sync.Mutex{}
    widgetInventory int32= 1000
    newPurchase = sync.NewCond(&mutex)
)

func main() {
    fmt.Println("Starting inventory count = ", widgetInventory)
    wg.Add(2)
    go makeSales()
    go newPurchases()
    wg.Wait()
    fmt.Println("Ending inventory count = ", widgetInventory)
}

func makeSales() {
    for i := 0; i < 3000; i++ {
        mutex.Lock()
        if widgetInventory-100 < 0{
            newPurchase.Wait()
        }
        widgetInventory -= 100
        fmt.Println(widgetInventory)
        mutex.Unlock()
    }
    wg.Done()
}

func newPurchases() {
    for i := 0; i < 3000; i++ {
        mutex.Lock()
        widgetInventory+= 100
        fmt.Println(widgetInventory)
        newPurchase.Signal()
        mutex.Unlock()
    }
    wg.Done()
}

核心原因:sync.Cond.Wait()的特殊行为

你之所以没看到死锁,是因为sync.Cond的Wait()方法不是单纯挂起goroutine,它做了两件关键事:

  • 自动释放持有的互斥锁:当makeSales里调用newPurchase.Wait()时,它会立刻把当前持有的mutex释放掉。这时候newPurchases goroutine就能抢到mutex,执行库存增加的操作。
  • 被唤醒后重新获取锁:当newPurchases调用newPurchase.Signal()时,会唤醒等待在这个条件变量上的makeSales goroutine。被唤醒的makeSales会先重新拿到mutex锁,然后才继续执行后续的库存扣减逻辑。

补充说明

另外要提一句,示例里用if判断库存其实是不严谨的——标准做法应该用for循环反复检查条件,因为存在虚假唤醒的可能(goroutine可能在没有收到Signal()/Broadcast()的情况下被唤醒)。不过这个问题不影响当前的死锁疑问,因为newPurchases会持续发送信号,最终总能满足库存条件。

最后,两个goroutine的循环次数都是3000次,每次操作100库存,初始库存1000,最终库存刚好回到初始值,整个逻辑可以正常跑完,不会出现无限等待的情况。

内容的提问来源于stack exchange,提问作者Mariusz Zieliński

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 23:35:10