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

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,理由还是上面提到的两点:

  1. 防止虚假唤醒:如果发生虚假唤醒,data仍然是空的,必须继续等待;
  2. 避免条件变化:如果后续有其他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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 19:57:28