为何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释放掉。这时候newPurchasesgoroutine就能抢到mutex,执行库存增加的操作。 - 被唤醒后重新获取锁:当
newPurchases调用newPurchase.Signal()时,会唤醒等待在这个条件变量上的makeSalesgoroutine。被唤醒的makeSales会先重新拿到mutex锁,然后才继续执行后续的库存扣减逻辑。
补充说明
另外要提一句,示例里用if判断库存其实是不严谨的——标准做法应该用for循环反复检查条件,因为存在虚假唤醒的可能(goroutine可能在没有收到Signal()/Broadcast()的情况下被唤醒)。不过这个问题不影响当前的死锁疑问,因为newPurchases会持续发送信号,最终总能满足库存条件。
最后,两个goroutine的循环次数都是3000次,每次操作100库存,初始库存1000,最终库存刚好回到初始值,整个逻辑可以正常跑完,不会出现无限等待的情况。
内容的提问来源于stack exchange,提问作者Mariusz Zieliński
相关产品推荐
相关产品推荐

