Condition Variables: 搞明白 go 语言的 sync.Cond

Condition Variables: 搞明白 go 语言的 sync.Cond

💡 原文中文,约4600字,阅读约需11分钟。
📝

内容提要

文章以雷达发现敌机、按距离唤醒不同导弹发射井为例,演示Go中sync.Cond的用法:多个goroutine修改共享位置后调用Broadcast,等待方在循环中检查条件并调用Wait,以应对虚假唤醒。文章还介绍Cond的源码结构(Locker与notifyList链表),并讨论有人提议在Go 2中移除sync.Cond、改用channel替代的争议。

🔎

延伸解读

为什么等待条件必须放在循环里

文章通过雷达与导弹发射井的例子强调,调用 Wait 后即使被唤醒,等待的条件也可能仍不成立,即虚假唤醒。因此每个等待方都在 for 循环中检查位置范围,只有条件满足才执行发射逻辑。这提醒开发者不能把 if 当作条件检查,否则可能在条件不满足时错误地继续执行,导致共享状态被误用。

Broadcast 与 channel 替代方案的取舍

文章提到有人提议在 Go 2 中移除 sync.Cond,用 channel 的 close 替代 Broadcast、用 send 替代 Signal。但作者指出 channel 关闭后无法再次打开,而 sync.Cond 可以反复 Broadcast。这意味着在需要多次唤醒所有等待者的场景中,sync.Cond 仍有其适用性,不能简单用一次性关闭的 channel 完全取代。

从源码看 Cond 的等待队列机制

文章简要剖析了 sync.Cond 的结构:它包含一个 Locker 和一个 notifyList。notifyList 以票据(ticket)方式维护等待唤醒的 goroutine 链表,wait 和 notify 分别记录下一个等待者和下一个将被通知者的编号。这种设计使得 Broadcast 能遍历通知所有等待者,而 Signal 只推进一个通知位置,帮助读者理解 Cond 内部如何管理等待与唤醒。

❓

Q&A

Go 语言里 sync.Cond 是什么,主要用来解决什么问题?

sync.Cond 是 Go 中的条件变量,用于让一个 goroutine 在某个条件不满足时进入等待状态,当条件满足时再被唤醒。它支持唤醒一个(Signal)或唤醒所有(Broadcast)等待者。

为什么使用 sync.Cond 的 Wait 时,必须把条件检查放在 for 循环里?

因为存在虚假唤醒(Spurious Wakeup),即 Wait 可能在条件并未真正满足的情况下返回。所以必须在 while/for 循环中重新检查条件,只有条件满足时才继续执行。

sync.Cond 的 Broadcast 和 Signal 有什么区别?

Broadcast 会唤醒所有正在等待的 goroutine,而 Signal 只唤醒一个等待的 goroutine。文章示例中雷达发现敌机后调用 Broadcast,让所有发射井检查条件,符合条件的被唤醒。

有人提议在 Go 2 中移除 sync.Cond,理由是什么?

提议者认为 (*sync.Cond).Broadcast 完全可以被 channel 的 close 取代,而 Signal 可以被 channel send 取代,因此 sync.Cond 是多余的。

为什么 channel 不能完全替代 sync.Cond?

虽然 channel 在某些场景下可以替代 sync.Cond,但 channel 关闭后就不能再次打开,而 sync.Cond 可以任意次调用 Broadcast(),因此 channel 无法完全替代。

sync.Cond 的内部结构是怎样的?

sync.Cond 包含一个 Locker 接口(L)和一个 notifyList 链表。notifyList 维护等待唤醒的 goroutine,是一个基于 ticket 的通知列表,包含 wait、notify 计数以及等待者链表(head、tail)。

🏷️

标签

➡️

继续阅读