Go语言中的Cond是一个条件变量,用于在多个goroutine之间同步地等待和通知。它可以用于实现线程间的互斥和同步。Cond通常与Mutex一起使用,以便在共享数据结构上进行访问控制。
sync.Cond 的定义
// Each Cond has an associated Locker L (often a *Mutex or *RWMutex),// which must be held when changing the condition and// when calling the Wait method.//// A Cond must not be copied after first use.type Cond struct {noCopy noCopy// L is held while observing or changing the conditionL Lockernotify notifyListchecker copyChecker}
每个 Cond 实例都会关联一个锁 L(互斥锁 Mutex,或读写锁 RWMutex),当修改条件或者调用 Wait 方法时,必须加锁。
在使用Cond时,通常需要与Mutex配合使用。
- 使用Mutex来保护共享数据结构;
- 在Cond等待前获取锁,等待条件变量;
- 在满足条件后使用Cond.Signal()或Cond.Broadcast()来唤醒等待的goroutine;释放锁。
- 这样可以确保在修改共享数据结构时不会有多个goroutine同时修改导致竞争条件。
Cond方法
- NewCond 创建实例,NewCond 创建 Cond 实例时,需要关联一个锁。
- Signal():S 只唤醒任意 1 个等待条件变量 c 的 goroutine,无需锁保护。
- Broadcast():Broadcast 唤醒所有等待条件变量 c 的 goroutine,无需锁保护。
- Wait():阻塞当前 goroutine 直到接收到通知,调用 Wait 会自动释放锁 c.L。
func NewCond(l Locker) *Condfunc (c *Cond) Broadcast()func (c *Cond) Signal()func (c *Cond) Wait()
sync.Cond 的使用场景
sync.Cond 条件变量用来协调想要访问共享资源的那些 goroutine,当共享资源的状态发生变化的时候,它可以用来通知被互斥锁阻塞的 goroutine。
sync.Cond 基于互斥锁/读写锁,它和互斥锁的区别是什么呢?
互斥锁 sync.Mutex 通常用来保护临界区和共享资源,条件变量 sync.Cond 用来协调想要访问共享资源的 goroutine。
sync.Cond 经常用在多个 goroutine 等待,一个 goroutine 通知(事件发生)的场景。
如果是一个通知,一个等待,使用互斥锁或 channel 就能搞定了。
使用示例
示例1
package mainimport ("fmt""sync""time")// 互斥锁需要保护的条件变量var done = false// 调用 Wait() 等待通知,直到 done 为 true。func read(name string, c *sync.Cond) {c.L.Lock()for !done {// 调用waitc.Wait()}fmt.Println(name, "--------------- starts reading")c.L.Unlock()}func write(name string, c *sync.Cond) {fmt.Println(name, "starts writing")// 暂停1stime.Sleep(time.Second)c.L.Lock()// 将 done 置为 truedone = truec.L.Unlock()fmt.Println(name, "wakes all")// Broadcastc.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(time.Second * 3)}
输出结果:
writer starts writingwriter wakes allreader1 --------------- starts readingreader3 --------------- starts readingreader2 --------------- starts reading
- done 即互斥锁需要保护的条件变量。
- read() 调用 Wait() 等待通知,直到 done 为 true。
- write() 接收数据,接收完成后,将 done 置为 true,调用 Broadcast() 通知所有等待的协程。
- write() 中的暂停了 1s,一方面是模拟耗时,另一方面是确保前面的 3 个 read 协程都执行到 Wait(),处于等待状态。main 函数最后暂停了 3s,确保所有操作执行完毕。
package mainimport ("fmt""log""math/rand""sync""time")var ready = 0var cond = sync.NewCond(&sync.Mutex{})var count = 0func main() {for i := 0; i < 10; i++ {go readyGo(i)}wake()log.Println("所有运动员都准备就绪,比赛开始。。。")}func readyGo(i int) {time.Sleep(time.Second * time.Duration(rand.Int63n(10)))// 加锁更改等待条件cond.L.Lock()ready++cond.L.Unlock()fmt.Printf("======运动员%d已准备就绪 \n", i)// 广播唤醒等待者,这里可以使用Broadcast和Signalcond.Signal()}func wake() {cond.L.Lock()for ready != 10 {cond.Wait()count++fmt.Printf("裁判员被唤醒%d次 \n", count)}cond.L.Unlock()}
示例2
10 个运动员进入赛场之后需要先做拉伸活动活动筋骨 ,在自己的赛道上做好准备;等所有的运动员都准备好之后,裁判员才会打响发令枪。
每个运动员做好准备之后,将 ready 加一,表明自己做好准备了,同时调用 Broadcast 方法通知裁判员。因为裁判员只有一个,所以这里可以直接替换成 Signal 方法调用。
调用 Broadcast 方法的时候,我们并没有请求 c.L 锁,只是在更改等待变量的时候才使用到了锁。
裁判员会等待运动员都准备好。虽然每个运动员准备好之后都唤醒了裁判员,但是裁判员被唤醒之后需要检查等待条件是否满足(运动员都准备好了)。可以看到,裁判员被唤醒之后一定要检查等待条件,如果条件不满足还是要继续等待。
======运动员7已准备就绪裁判员被唤醒1次======运动员0已准备就绪裁判员被唤醒2次======运动员5已准备就绪裁判员被唤醒3次======运动员4已准备就绪裁判员被唤醒4次======运动员9已准备就绪裁判员被唤醒5次======运动员8已准备就绪裁判员被唤醒6次======运动员6已准备就绪裁判员被唤醒7次======运动员3已准备就绪裁判员被唤醒8次======运动员1已准备就绪裁判员被唤醒9次======运动员2已准备就绪裁判员被唤醒10次所有运动员都准备就绪,比赛开始。。。
Cond实现原理
type Cond struct {noCopy noCopy// 当观察或者修改等待条件的时候需要加锁L Locker// 等待队列notify notifyListchecker copyChecker}func NewCond(l Locker) *Cond {return &Cond{L: l}}func (c *Cond) Wait() {c.checker.check()// 增加到等待队列中t := runtime_notifyListAdd(&c.notify)c.L.Unlock()// 阻塞休眠直到被唤醒runtime_notifyListWait(&c.notify, t)c.L.Lock()}func (c *Cond) Signal() {c.checker.check()runtime_notifyListNotifyOne(&c.notify)}func (c *Cond) Broadcast() {c.checker.check()runtime_notifyListNotifyAll(&c.notify)}
- runtime_notifyListXXX 是运行时实现的方法,实现了一个等待 / 通知的队列。
- copyChecker 是一个辅助结构,可以在运行时检查 Cond 是否被复制使用。
- Signal 和 Broadcast 只涉及到 notifyList 数据结构,不涉及到锁。
Wait 把调用者加入到等待队列时会释放锁,在被唤醒之后还会请求锁。在阻塞休眠期间,调用者是不持有锁的,这样能让其他 goroutine 有机会检查或者更新等待变量。
参考
- https://juejin.cn/post/7194704072136966181
- https://www.cnblogs.com/huiyichanmian/p/14463844.html
