Go语言sync.Cond的Wait方法疑问:为何必须循环调用?
关于sync.Cond循环调用Wait的疑问解答
为什么sync.Cond的Wait必须放在循环里?
先看官方文档的说明:
由于Wait等待期间c.L未被锁定,调用者通常不能假设Wait返回时条件为真。相反,调用者应在循环中调用Wait:
官方给出的标准写法:
c.L.Lock() for !condition() { c.Wait() } // 利用条件进行操作 c.L.Unlock()
核心原因有两个:
- 虚假唤醒:操作系统层面的条件变量存在「虚假唤醒」的可能——没有收到Signal/Broadcast信号,Wait也会自行返回。这时候条件根本不满足,必须重新检查。
- 条件被篡改:就算是正常被唤醒,当前goroutine重新获取锁的间隙里,其他goroutine可能已经修改了共享资源,导致原本满足的条件又失效了。比如多个goroutine等待同一个条件,第一个被唤醒的goroutine处理资源后改变了条件,后续被唤醒的goroutine必须重新检查。
你的代码是否需要循环调用Wait?
先看你写的subscribe函数:
c.L.Lock() for len(data) != 0 { c.Wait() } fmt.Printf("[%s] %s\n", name, data["key"]) c.L.Unlock()
这里首先存在逻辑错误:你写的循环条件是len(data) != 0时等待,但初始状态下data是空的,所以根本不会进入循环,会直接执行打印逻辑——此时data["key"]不存在,会触发panic。
正确的逻辑应该是等待data被填充,也就是循环条件改为len(data) == 0,这时候必须用循环调用Wait,理由还是上面提到的两点:
- 防止虚假唤醒:如果发生虚假唤醒,
data仍然是空的,必须继续等待; - 避免条件变化:如果后续有其他goroutine清空
data,即使被唤醒也需要重新检查条件是否满足。
修正后的代码示例
func subscribe(name string, data map[string]string, c *sync.Cond) { c.L.Lock() // 等待data中有数据,必须用循环 for len(data) == 0 { c.Wait() } fmt.Printf("[%s] %s\n", name, data["key"]) c.L.Unlock() } func publish(name string, data map[string]string, c *sync.Cond) { time.Sleep(time.Second) c.L.Lock() data["key"] = "value" c.L.Unlock() fmt.Printf("[%s] data publisher\n", name) c.Broadcast() } func main() { data := map[string]string{} cond := sync.NewCond(&sync.Mutex{}) wg := sync.WaitGroup{} wg.Add(3) go func() { defer wg.Done() subscribe("subscriber_1", data, cond) }() go func() { defer wg.Done() subscribe("subscriber_2", data, cond) }() go func() { defer wg.Done() publish("publisher", data, cond) }() wg.Wait() }
内容的提问来源于stack exchange,提问作者QuickDzen
相关产品推荐
相关产品推荐

