条件变量是go中协调goroutine的同步机制,需与互斥锁配合使用。sync.Cond提供Wait、signal、Broadcast方法,用于等待条件满足后唤醒goroutine。消费者用for循环加Wait等待,生产者修改状态后调用Signal或Broadcast通知,确保安全访问共享数据,避免竞态条件和虚假唤醒。

在golang中,条件变量用于协调多个goroutine之间的执行顺序,尤其适用于等待某个条件成立后再继续执行的场景。Go语言通过 sync.Cond 类型提供了对条件变量的支持,它通常与互斥锁(sync.Mutex 或 sync.RWMutex)配合使用,确保共享数据的安全访问。
什么是条件变量
条件变量是一种同步机制,允许goroutine在某个条件不满足时挂起等待,并在其他goroutine改变状态后被唤醒。它不是用来保护临界区的,而是用来“等待-通知”协作。
sync.Cond 包含三个核心方法:
- Wait():释放锁并阻塞当前goroutine,直到被 Signal 或 Broadcast 唤醒。
- Signal():唤醒一个正在等待的goroutine。
- Broadcast():唤醒所有等待的goroutine。
如何正确使用 sync.Cond
使用条件变量的关键是结合互斥锁和循环检查条件,避免虚假唤醒或竞态条件。
立即学习“go语言免费学习笔记(深入)”;
示例:生产者-消费者模型
假设有一个共享缓冲区,生产者向其中添加数据,消费者等待数据可用后再读取。
package main <p>import ( "fmt" "sync" "time" )</p><p>func main() { var mu sync.Mutex cond := sync.NewCond(&mu) items := make([]int, 0, 10)</p><pre class='brush:php;toolbar:false;'>// 消费者 goroutine go func() { mu.Lock() for len(items) == 0 { cond.Wait() // 等待有数据 } // 取出数据(实际项目中可能需要更复杂的逻辑) item := items[0] items = items[1:] fmt.Printf("消费了: %dn", item) mu.Unlock() }() // 生产者 goroutine go func() { mu.Lock() items = append(items, 42) fmt.Println("生产了数据") cond.Signal() // 通知等待的消费者 mu.Unlock() }() // 主线程等待一段时间让goroutine完成 time.Sleep(1 * time.Second)
}
说明:
- 消费者在进入 Wait 前必须持有锁,并用 for 循环检查条件,防止虚假唤醒。
- Wait 方法会自动释放锁,当被唤醒后重新获取锁再返回。
- 生产者修改数据后调用 Signal 通知至少一个等待者。
广播通知 Broadcast 的使用场景
当你有多个等待者,并且一次状态变化影响所有等待者时,应使用 Broadcast。
例如,关闭服务、清空队列等全局操作。
// 唤醒所有等待的goroutine cond.Broadcast()
比如多个消费者等待同一个队列,生产者一次性放入多个元素,可以用 Broadcast 让所有消费者尝试获取任务。
注意事项与最佳实践
- 总是使用 for 循环检查条件,而不是 if,以应对虚假唤醒。
- 每次调用 Wait 前必须持有锁。
- Signal 和 Broadcast 应在改变条件状态后调用。
- 即使没有goroutine在等待,调用 Signal 或 Broadcast 也不会出错。
- 注意死锁风险:不要在未解锁的情况下长时间运行或再次等待。
基本上就这些。sync.Cond 虽不如 channel 常见,但在某些需要精确控制唤醒行为的场景下非常有用。


