sync.Cond 源码分析

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
	}
}
  • 9
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值