Go语言sync.Cond详解:条件变量与广播唤醒机制
Go语言的sync.Cond是一种条件变量,用于协调多个Goroutine对共享资源的访问。它基于互斥锁工作,专门解决当多个协程需要等待某个条件满足(如数据写入完成)时才继续执行的问题,相比轮询或单一通道通知,能更高效地批量唤醒阻塞的协程。
sync.Cond的核心用途
sync.Cond(位于sync包)实现了POSIX标准中的条件变量机制。它的核心作用是协调那些希望共享同一公共资源的Goroutine。当共享资源的状态发生变化时,可以通过它通知正在因该资源而阻塞的Goroutine。
每个Cond实例都必须关联一个锁(通常是*sync.Mutex或*sync.RWMutex)。在修改条件状态或调用Wait方法时,必须持有该锁,以保护条件的原子性。这一机制类似于Java中的wait()和notifyAll()。
sync.Cond 与 sync.Mutex 的区别
虽然两者都涉及并发控制,但职责不同:
sync.Mutex:主要用于保护临界区,防止多个Goroutine同时修改共享资源,确保互斥访问。sync.Cond:基于互斥锁,用于“等待”和“通知”。它让Goroutine能够暂停执行,直到特定的布尔条件变为真,并在条件变化后唤醒其他等待的Goroutine。
典型使用场景
考虑以下情形:一个Writer Goroutine负责接收并处理数据,多个Reader Goroutine需要等待数据处理完毕后才能读取正确的结果。
如果仅使用Channel或互斥锁,通常只能通知单个等待者,或者需要复杂的逻辑来分发通知。此时可以使用以下几种方案,其中sync.Cond是最直接的内建解决方案:
- 轮询检查:设置全局标志位,Reader反复检查标志值直到数据就绪。缺点是会消耗CPU资源。
- 多Channel通知:为每个Reader创建独立Channel,Writer完成后挨个发送信号。实现复杂且难以扩展。
- 使用 sync.Cond:内置支持批量通知,无需额外数据结构,逻辑清晰且高效。
sync.Cond 结构与方法
在内部,Cond维护了一个Locker接口和内部的通知列表。关键规则:Cond在使用前必须初始化,且创建后不可复制。
NewCond 创建实例
使用sync.NewCond(l Locker)创建一个条件变量实例,传入所需的锁对象。
cond := sync.NewCond(&sync.Mutex{})
Broadcast 广播唤醒所有
调用c.Broadcast()会唤醒所有等待在该条件变量上的Goroutine。此调用不要求调用者持有锁,但为了保证逻辑正确,通常建议在改变条件后持有锁调用。
// 示例:定时广播唤醒(实际生产中需配合真实条件判断)
go func() {
for range time.Tick(1 * time.Millisecond) {
cond.Broadcast()
}
}()
Signal 唤醒一个协程
调用c.Signal()仅唤醒任意一个等待的Goroutine。这类似于Java中的notify()。适用于只需唤醒单个消费者即可继续处理的场景。
Wait 等待条件满足
c.Wait()是核心阻塞操作。它原子性地执行以下两步:
- 释放关联的锁
c.L。 - 挂起当前Goroutine,直到被
Signal或Broadcast唤醒。
当被唤醒后,Wait会重新获取锁c.L并返回。**重要提示**:由于虚假唤醒或条件竞争,Wait返回时不能直接假设条件为真,必须将其置于for循环中检查条件。
c.L.Lock()
for !condition() {
c.Wait()
}
// 条件满足,使用资源
c.L.Unlock()
完整代码示例
以下示例演示了Writer写入数据后,通知所有处于等待状态的Reader进行读取的过程。
package main
import (
"fmt"
"sync"
"time"
)
var done = false
func read(name string, c *sync.Cond) {
c.L.Lock()
// 循环等待,直到done变为true
for !done {
c.Wait()
}
fmt.Println(name, "starts reading")
c.L.Unlock()
}
func write(name string, c *sync.Cond) {
fmt.Println(name, "starts writing")
time.Sleep(time.Second) // 模拟写入耗时
c.L.Lock()
done = true
c.L.Unlock()
fmt.Println(name, "wakes all readers")
c.Broadcast() // 唤醒所有等待者
}
func main() {
cond := sync.NewCond(&sync.Mutex{})
go read("reader1", cond)
go read("reader2", cond)
go read("reader3", cond)
write("writer", cond)
time.Sleep(3 * time.Second)
}
运行逻辑分析:
writer启动并开始模拟工作,随后三个reader启动。reader进入Wait循环,因为done=false,它们依次释放锁并挂起。writer睡眠结束后,锁定Mutex,将done设为true并解锁。writer调用Broadcast,所有被挂起的reader被唤醒并竞争锁。- 获锁的
reader再次检查!done,发现条件已满足,退出循环并打印读取日志。
注意事项与排错
- 必须在锁内使用Wait:
Wait调用前必须持有锁,否则会导致 panic。 - 避免虚假唤醒:始终使用
for而非if包裹Wait,以防止在条件未真正满足时被意外唤醒导致错误逻辑。 - 锁的范围:在
Wait返回后、使用资源前,确保仍然持有锁;使用完毕后务必Unlock。 - 死锁风险:如果在
Broadcast或Signal之前没有正确设置条件变量,或者忘记调用通知方法,等待的协程将永远阻塞。
以上就是Go语言中sync.Cond使用详解的详细内容,更多关于Go语言同步原语的资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













