Go Cond实现原理

概念

Go标准库提供了Cond原语,可以让 Goroutine 在满足特定条件时被阻塞和唤醒

底层数据结构

  1. type Cond struct {
  2. noCopy noCopy
  3. // L is held while observing or changing the condition
  4. L Locker
  5. notify notifyList
  6. checker copyChecker
  7. }
  8. type notifyList struct {
  9. wait uint32
  10. notify uint32
  11. lock uintptr // key field of the mutex
  12. head unsafe.Pointer
  13. tail unsafe.Pointer
  14. }

主要有4个字段:

  • nocopy : golang 源码中检测禁止拷贝的技术。如果程序中有 WaitGroup 的赋值行为,使用 go vet 检查程序时,就会发现有报错,但需要注意的是,noCopy 不会影响程序正常的编译和运行
  • checker:用于禁止运行期间发生拷贝,双重检查(Double check)
  • L:可以传入一个读写锁或互斥锁,当修改条件或者调用Wait方法时需要加锁
  • notify:通知链表,调用Wait()方法的Goroutine会放到这个链表中,从这里获取需被唤醒的Goroutine列表

使用方法

在Cond里主要有3个方法:

  • sync.NewCond(l Locker): 新建一个 sync.Cond 变量,注意该函数需要一个 Locker 作为必填参数,这是因为在 cond.Wait() 中底层会涉及到 Locker 的锁操作

  • Cond.Wait(): 阻塞等待被唤醒,调用Wait函数前需要先加锁;并且由于Wait函数被唤醒时存在虚假唤醒等情况,导致唤醒后发现,条件依旧不成立,因此需要使用 for 语句来循环地进行等待,直到条件成立为止

  • Cond.Signal(): 只唤醒一个最先 Wait 的 goroutine,可以不用加锁

  • Cond.Broadcast(): 唤醒所有Wait的goroutine,可以不用加锁

  1. package main
  2. import (
  3. "fmt"
  4. "sync"
  5. "sync/atomic"
  6. "time"
  7. )
  8. var status int64
  9. func main() {
  10. c := sync.NewCond(&sync.Mutex{})
  11. for i := 0; i < 10; i++ {
  12. go listen(c)
  13. }
  14. go broadcast(c)
  15. time.Sleep(1 * time.Second)
  16. }
  17. func broadcast(c *sync.Cond) {
  18. // 原子操作
  19. atomic.StoreInt64(&status, 1)
  20. c.Broadcast()
  21. }
  22. func listen(c *sync.Cond) {
  23. c.L.Lock()
  24. for atomic.LoadInt64(&status) != 1 {
  25. c.Wait()
  26. // Wait 内部会先调用 c.L.Unlock(),来先释放锁,如果调用方不先加锁的话,会报错
  27. }
  28. fmt.Println("listen")
  29. c.L.Unlock()
  30. }