需要唤醒一个或所有的 waiter 做一些检查操作的时候, 可以使用条件变量(Cond) 同步原语. 条件变量Cond 有一个目的, 就是一个或者多个 goroutine 等待目标条件得到满足, 如果目标条件得不到满足, 这些 goroutine 就会被阻塞. 如果其他 goroutine 改变了条件, 则会通知一个或所有被阻塞的 goroutine, 再次检查判断条件.

Cond 的使用方法

Go 标准库提供 Cond 同步原语的目的是为等待/通知场景下的并发操作提供支持. Cond 通常用于等待某个条件的一组 goroutine, 当条件变为 true 时, 其中一个或者所有的 goroutine 会被唤醒执行.

顾名思义, Cond 与某个条件相关, 这个条件需要一组 goroutine 协作达到. 当这个条件没有得到满足时, 所有等待这个条件的 goroutine 都会被阻塞, 只有当这组 goroutine 通过协作达到了这个条件时, 等待的 goroutine 才可能继续执行.

那么, 等待的条件是某个变量达到某个阈值或者某个时间点, 也可以是一组变量达到了某个阈值, 还可以是某个对象的状态满足了特定的条件. 总体来讲, 等待的条件是一种可以用来计算结果是 true 还是 false 的条件

Go 标准库中的 Cond 同步原语初始化时, 需要关联一个 Locker 接口的实例, 一般使用 Mutex 或者 RWMutex.

type Cond
	func NewCond(l Locker) *Cond
	func (c *Cond) Broadcast()
	func (c *Cond) Signal()
	func (c *Cond) Wait()
import (
	"log"
	"math/rand"
	"sync"
	"time"
)

func RunDemo() {
	c := sync.NewCond(&sync.Mutex{})

	var ready int

	for i := 0; i < 10; i++ {
		go func(i int) {
			time.Sleep((time.Duration(rand.Int63n(10)) * time.Second))

			// 加锁, 更改等待的条件
			c.L.Lock()
			ready++
			c.L.Unlock()

			log.Printf("运动员 #%d 已准备就绪\\n", i)

			c.Broadcast() // 运动员 i 准备就绪, 通知所有裁判员
		}(i)
	}

	c.L.Lock()
	for ready != 10 { // 检查条件是否满足
		c.Wait()
		log.Println("裁判员被唤醒一次")
	}
	c.L.Unlock()

	log.Println("所有运动员已准备就绪")
}

这些都是必须, 否则程序可能出现 panic, 或者程序还没等到条件满足就继续执行了

Cond 的实现

Cond 的实现非常简单, 因为复杂的逻辑已经被 Locker 或者运行时(runtime)的等待队列实现了.

type Cond struct {
	noCopy noCopy
	
	// 在检查条件或者修改条件时需要持有锁
	L Locker

	notify  notifyList
	checker copyChecker
}

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) // 通知一个 waiter
}

func (c *Cond) Broadcast() {
	c.checker.check()
	runtime_notifyListNotifyAll(&c.notify) // 通知所有的 waiter
}

type copyChecker uintptr

func (c *copyChecker) check() { // 检查有没有被复制
	if uintptr(*c) != uintptr(unsafe.Pointer(c)) &&
		!atomic.CompareAndSwapUintptr((*uintptr)(c), 0, uintptr(unsafe.Pointer(c))) &&
		uintptr(*c) != uintptr(unsafe.Pointer(c)) { // 双重检查
		panic("sync.Cond is copied")
	}
}

主要逻辑已经被运行时的 runtime_notifyListXXX 实现了.