首先会判断old状态,如果没有饥饿,也没有获取到锁,那么直接返回,因为这种情况在进入到这段代码之前会将new状态设置为mutexLocked,表示已经获取到锁。这里还判断了一下old状态不能为饥饿状态,否则也不能获取到锁;
判断waitStartTime是否已经初始化过了,如果是新的goroutine来抢占锁,那么queueLifo会返回false;如果不是新的goroutine来抢占锁,那么加入到等待队列头部,这样等待最久的 goroutine 优先能够获取到锁;
如果等待时间为0,那么初始化等待时间;
阻塞等待,当前goroutine进行休眠;
唤醒之后检查锁是否应该处于饥饿状态,并设置starving变量值;
判断是否已经处于饥饿状态,如果不处于饥饿状态,那么这里直接进入到下一个for循环中获取锁;
加锁并且将waiter数减1,这里我看了一会,没用懂什么意思,其实需要分两步来理解,相当于state+mutexLocked,然后state再将waiter部分的数减一;
如果当前goroutine不是饥饿状态或者waiter只有一个,就从饥饿模式切换会正常模式;
设置状态;
下面用图例来解释:
这部分的图解是休眠前的操作,休眠前会根据old的状态来判断能不能直接获取到锁,如果old状态没有上锁,也没有饥饿,那么直接break返回,因为这种情况会在CAS中设置加上锁;
接着往下判断,waitStartTime是否等于0,如果不等于,说明不是第一次来了,而是被唤醒后来到这里,那么就不能直接放到队尾再休眠了,而是要放到队首,防止长时间抢不到锁;
下面这张图是处于唤醒后的示意图,如何被唤醒的可以直接到跳到解锁部分看完再回来。
被唤醒一开始是需要判断一下当前的starving状态以及等待的时间如果超过了1ms,那么会将starving设置为true;
接下来会有一个if判断, 这里有个细节,因为是被唤醒的,所以判断前需要重新获取一下锁,如果当前不是饥饿模式,那么会直接返回,然后重新进入到for循环中;
如果当前是处于饥饿模式,那么会计算一下delta为加锁,并且当前的goroutine是可以直接抢占锁的,所以需要将waiter减一,如果starving不为饥饿,或者等待时间没有超过1ms,或者waiter只有一个了,那么还需要将delta减去mutexStarving,表示退出饥饿模式;
最后通过AddInt32将state加上delta,这里之所以可以直接加上,因为这时候state的mutexLocked值肯定为0,并且mutexStarving位肯定为1,并且在获取锁之前至少还有当前一个goroutine在等待队列中,所以waiter可以直接减1。
解锁流程 fast path func (m *Mutex) Unlock() { if race.Enabled { _ = m.state race.Release(unsafe.Pointer(m)) } //返回一个state被减后的值 new := atomic.AddInt32(&m.state, -mutexLocked) if new != 0 { //如果返回的state值不为0,那么进入到unlockSlow中 m.unlockSlow(new) } }这里主要就是AddInt32重新设置state的mutexLocked位为0,然后判断新的state值是否不为0,不为0则调用unlockSlow方法。
unlockSlowunlockSlow方法里面也分为正常模式和饥饿模式下的解锁:
func (m *Mutex) unlockSlow(new int32) { if (new+mutexLocked)&mutexLocked == 0 { throw("sync: unlock of unlocked mutex") } // 正常模式 if new&mutexStarving == 0 { old := new for { // 如果没有 waiter,或者已经有在处理的情况,直接返回 if old>>mutexWaiterShift == 0 || old&(mutexLocked|mutexWoken|mutexStarving) != 0 { return } // waiter 数减 1,mutexWoken 标志设置上,通过 CAS 更新 state 的值 new = (old - 1<<mutexWaiterShift) | mutexWoken if atomic.CompareAndSwapInt32(&m.state, old, new) { // 直接唤醒等待队列中的 waiter runtime_Semrelease(&m.sema, false, 1) return } old = m.state } } else { // 饥饿模式 // 直接唤醒等待队列中的 waiter runtime_Semrelease(&m.sema, true, 1) } }