sync.Cond采用一个L锁进行保护条件变量,并使用通知队列来堵塞和通知goroutine
// Cond implements a condition variable, a rendezvous point
// for goroutines waiting for or announcing the occurrence
// of an event.
//
// 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是用于观察和改变条件的锁
L Locker
// 通知队列
notify notifyList
checker copyChecker
}
// NewCond returns a new Cond with Locker l.
func NewCond(l Locker) *Cond {
return &Cond{L: l}
}
notifyList
通知队列就是普通链表+锁+通知等待数量
type notifyList struct {
// 记录等待数量
wait uint32
// 记录通知数量
notify uint32
// 通知队列锁
lock uintptr // key field of the mutex
// 链表的头尾
head unsafe.Pointer
tail unsafe.Pointer
}
Wait()
wait()方法先递增等待数量,使得在解锁之后,signal()、broadcast()能够知道有等待者及时返回,防止不必要的逻辑处理
若在解锁之后,没有通知,就添加到通知队列,等待唤醒
func (c *Cond) Wait() {
c.checker.check()
// 添加到通知队列
t := runtime_notifyListAdd(&c.notify)
// 解锁并等待
c.L.Unlock()
runtime_notifyListWait(&c.notify, t)
// 唤醒后加锁
c.L.Lock()
}
// 标记添加调用者到通知列表,并返回ticket
func notifyListAdd(l *notifyList) uint32 {
// 在采用读写锁的时候,Wait方法也可能同时被调用,因此通过原子操作防止冲突
return atomic.Xadd(&l.wait, 1) - 1
}
// 等待通知。如果已经有通知了,那么立刻返回,否则堵塞
func notifyListWait(l *notifyList, t uint32) {
lockWithRank(&l.lock, lockRankNotifyList)
// 如果该ticket已经被通知,则立刻返回
if less(t, l.notify) {
unlock(&l.lock)
return
}
// 从池中获取一个等待列表g sudog,并更新ticket、释放时间等
s := acquireSudog()
s.g = getg()
s.ticket = t
s.releasetime = 0
t0 := int64(0)
if blockprofilerate > 0 {
t0 = cputicks()
s.releasetime = -1
}
// 添加goroutine到等待队列尾部
if l.tail == nil {
l.head = s
} else {
l.tail.next = s
}
l.tail = s
// 将 M/P/G 解绑,并将 G 调整为等待状态,放入 sudog 等待队列中
// 通过调用goready(gp)可以使该例程再次可运行
goparkunlock(&l.lock, waitReasonSyncCondWait, traceEvGoBlockCond, 3)
if t0 != 0 {
blockevent(s.releasetime-t0, 2)
}
// 释放sudog到池
releaseSudog(s)
}
func less(a, b uint32) bool {
return int32(a-b) < 0
}
Signal()
Signal()唤醒最早等待的,通过notifyList记录的第一个未唤醒,来遍历队列进行唤醒。由于链表基本上按照先后顺序加入,所以很快就找到了
// Signal wakes one goroutine waiting on c, if there is any.
//
// It is allowed but not required for the caller to hold c.L
// during the call.
func (c *Cond) Signal() {
c.checker.check()
runtime_notifyListNotifyOne(&c.notify)
}
func notifyListNotifyOne(l *notifyList) {
// 若等待和通知的数量相同,说明没有需要通知的,直接返回
if atomic.Load(&l.wait) == atomic.Load(&l.notify) {
return
}
lockWithRank(&l.lock, lockRankNotifyList)
// 双重锁检验,是否有需要通知的
// 设置要唤醒的ticket为队列第一个未唤醒的,这样就使得先来先通知
t := l.notify
if t == atomic.Load(&l.wait) {
unlock(&l.lock)
return
}
// 更新下一个通知的ticket
atomic.Store(&l.notify, t+1)
// 尝试遍历队列找到需要被通知的goroutine
for p, s := (*sudog)(nil), l.head; s != nil; p, s = s, s.next {
// 若当前等待ticket等于被通知的ticket
// 则从队列中取出该节点,并调用goready通知
if s.ticket == t {
n := s.next
if p != nil {
p.next = n
} else {
l.head = n
}
if n == nil {
l.tail = p
}
unlock(&l.lock)
s.next = nil
readyWithTime(s, 4)
return
}
}
unlock(&l.lock)
}
Broadcast()
与Signal()基本类似,只是不需要找特定ticket的goroutine才进行通知,而是唤醒队列中所有goroutine
func notifyListNotifyAll(l *notifyList) {
// Fast-path: if there are no new waiters since the last notification
// we don't need to acquire the lock.
if atomic.Load(&l.wait) == atomic.Load(&l.notify) {
return
}
// Pull the list out into a local variable, waiters will be readied
// outside the lock.
lockWithRank(&l.lock, lockRankNotifyList)
s := l.head
l.head = nil
l.tail = nil
// Update the next ticket to be notified. We can set it to the current
// value of wait because any previous waiters are already in the list
// or will notice that they have already been notified when trying to
// add themselves to the list.
atomic.Store(&l.notify, atomic.Load(&l.wait))
unlock(&l.lock)
// Go through the local list and ready all waiters.
for s != nil {
next := s.next
s.next = nil
readyWithTime(s, 4)
s = next
}
}