type Cond struct {
L Locker
notify notifyList
}
type notifyList struct {
wait uint32 //表示当前 Wait 的最大 ticket 值
notify uint32 //表示目前已唤醒的 goroutine 的 ticket 的最大值
lock uintptr // key field of the mutex
head unsafe.Pointer //链表头
tail unsafe.Pointer //链表尾
}
相关函数
func (c *Cond) Wait() {
// 获取当前ticket,即wait的值,实际是atomic.Xadd(&l.wait, 1) - 1,这样wait原子加1,
t := runtime_notifyListAdd(&c.notify)
// 注意这里,必须先解锁,因为 runtime_notifyListWait 要切走 goroutine
// 所以这里要解锁,要不然其他 goroutine 没法获取到锁了
//功能是将当前 goroutine 插入到 notifyList 链表
c.L.Unlock()
runtime_notifyListWait(&c.notify, t)
// 这里已经唤醒了,因此需要再度锁上
c.L.Lock()
}
func (c *Cond) () {
runtime_notifyListNotifyOne(&c.notify)
}
//notifyList 是一个链表结构,我们为何不直接取链表最头部唤醒呢
//因为 notifyList 会有乱序的可能,获取 ticket 和加入 notifyList
//是两个独立的行为,并发操作有可能的乱序,大部分场景下比较一两次之后就会很快停止
func notifyListNotifyOne(l *notifyList) {
***
for p, s := (*sudog)(nil), l.head; s != nil; p, s = s, s.next {
if s.ticket == t {
n := s.next
if p != nil {
p.next = n
} else {
l.head = n
}
if n == nil {
l.tail = p
}
s.next = nil
return
}
}
***
}
func (c *Cond) Broadcast() {
runtime_notifyListNotifyAll(&c.notify)
}
//唤醒所有等待的goruntine
fuc notifyListNotifyAll(l *notifyList) {
// Go through the local list and ready all waiters.
for s != nil {
next := s.next
s.next = nil
readyWithTime(s, 4)
s = next
}
}
自旋:新goroutine或被唤醒的goroutine首次获取不到锁,就会自旋(spin,通过循环不断尝试,在runtime实现的)的方式尝试检查锁,不需要上下文切换,提供性能。
饥饿模式:高并发情况下,新来的g一直自旋,waiter可能悲剧地获取不到锁。故waiter获取不到锁的时间超过阈值1毫秒,它会被插入到队列的前面,这个Mutex进入饥饿模式。
在饥饿模式下,Mutex的拥有者将直接把锁交给队列最前面的waiter。新的g不会尝试获取锁,不抢也不spin,乖乖地加入到等待队列的尾部。
如果拥有Mutex的waiter发现下面两种情况的其中之一,它就会把这个Mutex转换成正常模式:
// A Locker represents an object that can be locked and unlocked.
type Locker interface { //锁接口
Lock()
Unlock()
}
type Mutex struct {//互斥锁实例
state int32 //状态
sema uint32 //信号量
}
const (
mutexLocked = 1 << iota //0x0000 0001
mutexWoken //0x0000 0010
mutexStarving //0x0000 0100
mutexWaiterShift = iota 3 //等待者数量的标志,最多可以阻塞2^29个goroutine。
starvationThresholdNs = 1e6
)
mutexLocked代表锁的状态,如果为1代表已加锁
mutexWoken代表是否唤醒,如果为 1代表已唤醒
mutexStarving代表是否处于饥饿模式,如果为1 代表是
mutexWaiterShift 值是3,有人会问这里为什么不左移,而是直接是3,这是因为3这个会有1<<mutexWaiterShift代表1个waiter数量,也有右移代表获取mutex上面的waiter数量。
starvationThresholdNs代表判断饥饿模式的时间,如果等待时间超过这个时间就判断为饥饿
func (m *Mutex) Lock() {
// Slow path (outlined so that the fast path can be inlined)
m.lockSlow() //补贴源码,看图了
}
解锁,别贴代码,上图