Go语言中的Cond是一个条件变量,用于在多个goroutine之间同步地等待和通知。它可以用于实现线程间的互斥和同步。Cond通常与Mutex一起使用,以便在共享数据结构上进行访问控制。

sync.Cond 的定义

  1. // Each Cond has an associated Locker L (often a *Mutex or *RWMutex),
  2. // which must be held when changing the condition and
  3. // when calling the Wait method.
  4. //
  5. // A Cond must not be copied after first use.
  6. type Cond struct {
  7. noCopy noCopy
  8. // L is held while observing or changing the condition
  9. L Locker
  10. notify notifyList
  11. checker copyChecker
  12. }

每个 Cond 实例都会关联一个锁 L(互斥锁 Mutex,或读写锁 RWMutex),当修改条件或者调用 Wait 方法时,必须加锁。
在使用Cond时,通常需要与Mutex配合使用。

  1. 使用Mutex来保护共享数据结构;
  2. 在Cond等待前获取锁,等待条件变量;
  3. 在满足条件后使用Cond.Signal()或Cond.Broadcast()来唤醒等待的goroutine;释放锁。
  4. 这样可以确保在修改共享数据结构时不会有多个goroutine同时修改导致竞争条件。

Cond方法

  • NewCond 创建实例,NewCond 创建 Cond 实例时,需要关联一个锁。
  • Signal():S 只唤醒任意 1 个等待条件变量 c 的 goroutine,无需锁保护。
  • Broadcast():Broadcast 唤醒所有等待条件变量 c 的 goroutine,无需锁保护。
  • Wait():阻塞当前 goroutine 直到接收到通知,调用 Wait 会自动释放锁 c.L。
    1. func NewCond(l Locker) *Cond
    2. func (c *Cond) Broadcast()
    3. func (c *Cond) Signal()
    4. func (c *Cond) Wait()

    sync.Cond 的使用场景

    sync.Cond 条件变量用来协调想要访问共享资源的那些 goroutine,当共享资源的状态发生变化的时候,它可以用来通知被互斥锁阻塞的 goroutine。

sync.Cond 基于互斥锁/读写锁,它和互斥锁的区别是什么呢?

互斥锁 sync.Mutex 通常用来保护临界区和共享资源,条件变量 sync.Cond 用来协调想要访问共享资源的 goroutine。
sync.Cond 经常用在多个 goroutine 等待,一个 goroutine 通知(事件发生)的场景。
如果是一个通知,一个等待,使用互斥锁或 channel 就能搞定了。

使用示例

示例1

https://geektutu.com/post/hpg-sync-cond.html

  1. package main
  2. import (
  3. "fmt"
  4. "sync"
  5. "time"
  6. )
  7. // 互斥锁需要保护的条件变量
  8. var done = false
  9. // 调用 Wait() 等待通知,直到 done 为 true。
  10. func read(name string, c *sync.Cond) {
  11. c.L.Lock()
  12. for !done {
  13. // 调用wait
  14. c.Wait()
  15. }
  16. fmt.Println(name, "--------------- starts reading")
  17. c.L.Unlock()
  18. }
  19. func write(name string, c *sync.Cond) {
  20. fmt.Println(name, "starts writing")
  21. // 暂停1s
  22. time.Sleep(time.Second)
  23. c.L.Lock()
  24. // 将 done 置为 true
  25. done = true
  26. c.L.Unlock()
  27. fmt.Println(name, "wakes all")
  28. // Broadcast
  29. c.Broadcast()
  30. }
  31. func main() {
  32. cond := sync.NewCond(&sync.Mutex{})
  33. go read("reader1", cond)
  34. go read("reader2", cond)
  35. go read("reader3", cond)
  36. write("writer", cond)
  37. time.Sleep(time.Second * 3)
  38. }

输出结果:

  1. writer starts writing
  2. writer wakes all
  3. reader1 --------------- starts reading
  4. reader3 --------------- starts reading
  5. reader2 --------------- starts reading
  • done 即互斥锁需要保护的条件变量。
  • read() 调用 Wait() 等待通知,直到 done 为 true。
  • write() 接收数据,接收完成后,将 done 置为 true,调用 Broadcast() 通知所有等待的协程。
  • write() 中的暂停了 1s,一方面是模拟耗时,另一方面是确保前面的 3 个 read 协程都执行到 Wait(),处于等待状态。main 函数最后暂停了 3s,确保所有操作执行完毕。
  1. package main
  2. import (
  3. "fmt"
  4. "log"
  5. "math/rand"
  6. "sync"
  7. "time"
  8. )
  9. var ready = 0
  10. var cond = sync.NewCond(&sync.Mutex{})
  11. var count = 0
  12. func main() {
  13. for i := 0; i < 10; i++ {
  14. go readyGo(i)
  15. }
  16. wake()
  17. log.Println("所有运动员都准备就绪,比赛开始。。。")
  18. }
  19. func readyGo(i int) {
  20. time.Sleep(time.Second * time.Duration(rand.Int63n(10)))
  21. // 加锁更改等待条件
  22. cond.L.Lock()
  23. ready++
  24. cond.L.Unlock()
  25. fmt.Printf("======运动员%d已准备就绪 \n", i)
  26. // 广播唤醒等待者,这里可以使用Broadcast和Signal
  27. cond.Signal()
  28. }
  29. func wake() {
  30. cond.L.Lock()
  31. for ready != 10 {
  32. cond.Wait()
  33. count++
  34. fmt.Printf("裁判员被唤醒%d次 \n", count)
  35. }
  36. cond.L.Unlock()
  37. }

示例2

10 个运动员进入赛场之后需要先做拉伸活动活动筋骨 ,在自己的赛道上做好准备;等所有的运动员都准备好之后,裁判员才会打响发令枪。
每个运动员做好准备之后,将 ready 加一,表明自己做好准备了,同时调用 Broadcast 方法通知裁判员。因为裁判员只有一个,所以这里可以直接替换成 Signal 方法调用。
调用 Broadcast 方法的时候,我们并没有请求 c.L 锁,只是在更改等待变量的时候才使用到了锁。
裁判员会等待运动员都准备好。虽然每个运动员准备好之后都唤醒了裁判员,但是裁判员被唤醒之后需要检查等待条件是否满足(运动员都准备好了)。可以看到,裁判员被唤醒之后一定要检查等待条件,如果条件不满足还是要继续等待。

  1. ======运动员7已准备就绪
  2. 裁判员被唤醒1
  3. ======运动员0已准备就绪
  4. 裁判员被唤醒2
  5. ======运动员5已准备就绪
  6. 裁判员被唤醒3
  7. ======运动员4已准备就绪
  8. 裁判员被唤醒4
  9. ======运动员9已准备就绪
  10. 裁判员被唤醒5
  11. ======运动员8已准备就绪
  12. 裁判员被唤醒6
  13. ======运动员6已准备就绪
  14. 裁判员被唤醒7
  15. ======运动员3已准备就绪
  16. 裁判员被唤醒8
  17. ======运动员1已准备就绪
  18. 裁判员被唤醒9
  19. ======运动员2已准备就绪
  20. 裁判员被唤醒10
  21. 所有运动员都准备就绪,比赛开始。。。

Cond实现原理

  1. type Cond struct {
  2. noCopy noCopy
  3. // 当观察或者修改等待条件的时候需要加锁
  4. L Locker
  5. // 等待队列
  6. notify notifyList
  7. checker copyChecker
  8. }
  9. func NewCond(l Locker) *Cond {
  10. return &Cond{L: l}
  11. }
  12. func (c *Cond) Wait() {
  13. c.checker.check()
  14. // 增加到等待队列中
  15. t := runtime_notifyListAdd(&c.notify)
  16. c.L.Unlock()
  17. // 阻塞休眠直到被唤醒
  18. runtime_notifyListWait(&c.notify, t)
  19. c.L.Lock()
  20. }
  21. func (c *Cond) Signal() {
  22. c.checker.check()
  23. runtime_notifyListNotifyOne(&c.notify)
  24. }
  25. func (c *Cond) Broadcast() {
  26. c.checker.check()
  27. runtime_notifyListNotifyAll(&c.notify
  28. }