// 锁定Mutex. // If the lock is already in use, the calling goroutine // blocks until the mutex is available. func(m *Mutex)Lock() { // Fast path: grab unlocked mutex. if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) { if race.Enabled { race.Acquire(unsafe.Pointer(m)) } return }
var waitStartTime int64 starving := false awoke := false iter := 0 old := m.state for { // Don't spin in starvation mode, ownership is handed off to waiters // so we won't be able to acquire the mutex anyway. // 如果原状态为非饥饿模式的锁定状态,且canSpin(函数实现在下边,当iter<4、cpu数>1、至少由1个运行中的p、当前p的运行g队列为空,则可spin) if old&(mutexLocked|mutexStarving) == mutexLocked // 确定是已锁定的非饥饿状态 mutexLocked|mutexStarving = 00...00101 当且仅当old==1时 old & 101 == 1成立 && runtime_canSpin(iter) { // Active spinning makes sense. // Try to set mutexWoken flag to inform Unlock // to not wake other blocked goroutines. // 尝试设置mutexWoken标记来通知Unlock,不去唤醒其他阻塞的G // if !awoke && old&mutexWoken == 0 && // 再次确认未被唤醒 old>>mutexWaiterShift != 0 && // 确认有G在排队 atomic.CompareAndSwapInt32(&m.state, old, old|mutexWoken) { // 将对象锁置为唤醒状态
awoke = true// 标记当前G awoke } runtime_doSpin() // 空转,根据iter,重试一定次数将不再空转 iter++// 自旋次数 old = m.state // 更新old continue } new := old // 不可空转、 或 !非饥饿模式的锁定状态 // 不要试图获取饥饿的mutex,新来的G必须排队. if old&mutexStarving == 0 { // 若old是非饥饿模式,标记加锁 new |= mutexLocked } if old&(mutexLocked|mutexStarving) != 0 { // 如果old 已加锁/饥饿模式/加锁数!=0 则加锁数+1 new += 1 << mutexWaiterShift } // The current goroutine switches mutex to starvation mode.当前G切换到饥饿模式,但若mutex当前未加锁,则不切换 // But if the mutex is currently unlocked, don't do the switch. // Unlock expects that starving mutex has waiters, which will not // be true in this case. Unlock期望饥饿模式下有等待者,这种情况下不会为true if starving && old&mutexLocked != 0 { // 若处于饥饿模式,且(已加锁或有waiter),则new饥饿位做标记 new |= mutexStarving } if awoke { // 如果唤醒 // The goroutine has been woken from sleep, // so we need to reset the flag in either case. ifnew&mutexWoken == 0 { // 如果new的mutexWoken位为0,则抛异常 throw("sync: inconsistent mutex state") }// 将唤醒位置0 new &^= mutexWoken }// 获锁成功 if atomic.CompareAndSwapInt32(&m.state, old, new) { if old&(mutexLocked|mutexStarving) == 0 { // 如果old值锁位与饥饿位标志都为0,则说明获锁成功直接break break// locked the mutex with CAS } // If we were already waiting before, queue at the front of the queue. queueLifo := waitStartTime != 0// 如果队列不为0,则入队 if waitStartTime == 0 { waitStartTime = runtime_nanotime() } runtime_SemacquireMutex(&m.sema, queueLifo) starving = starving || runtime_nanotime()-waitStartTime > starvationThresholdNs old = m.state if old&mutexStarving != 0 { // If this goroutine was woken and mutex is in starvation mode, // ownership was handed off to us but mutex is in somewhat // inconsistent state: mutexLocked is not set and we are still // accounted as waiter. Fix that. if old&(mutexLocked|mutexWoken) != 0 || old>>mutexWaiterShift == 0 { throw("sync: inconsistent mutex state") } delta := int32(mutexLocked - 1<<mutexWaiterShift) if !starving || old>>mutexWaiterShift == 1 { // Exit starvation mode. // Critical to do it here and consider wait time. // Starvation mode is so inefficient, that two goroutines // can go lock-step infinitely once they switch mutex // to starvation mode. delta -= mutexStarving } atomic.AddInt32(&m.state, delta) break } awoke = true iter = 0 } else { old = m.state } }
if race.Enabled { race.Acquire(unsafe.Pointer(m)) } }
// runtime/proc.go linkname sync.runtime_canSpin() funcsync_runtime_canSpin(i int)bool { // sync.Mutex is cooperative, so we are conservative with spinning. // Spin only few times and only if running on a multicore machine and // GOMAXPROCS>1 and there is at least one other running P and local runq is empty. // As opposed to runtime mutex we don't do passive spinning here, // because there can be work on global runq or on other Ps. if i >= active_spin || ncpu <= 1 || gomaxprocs <= int32(sched.npidle+sched.nmspinning)+1 { returnfalse } if p := getg().m.p.ptr(); !runqempty(p) { returnfalse } returntrue }
// Unlock unlocks m. // It is a run-time error if m is not locked on entry to Unlock. // // 一个加锁状态的mutex不会与特定的G关联,允许在一个G加锁在另一个G解锁 // A locked Mutex is not associated with a particular goroutine. // It is allowed for one goroutine to lock a Mutex and then // arrange for another goroutine to unlock it. func(m *Mutex)Unlock() { if race.Enabled { _ = m.state race.Release(unsafe.Pointer(m)) }
// Fast path: drop lock bit. 快速路径,删除锁标记 new := atomic.AddInt32(&m.state, -mutexLocked) if (new+mutexLocked)&mutexLocked == 0 { throw("sync: unlock of unlocked mutex") } ifnew&mutexStarving == 0 { // 如果new的饥饿标记位为0 old := new for { // If there are no waiters or a goroutine has already // been woken or grabbed the lock, no need to wake anyone. // In starvation mode ownership is directly handed off from unlocking // goroutine to the next waiter. We are not part of this chain, // since we did not observe mutexStarving when we unlocked the mutex above. // So get off the way. // 如果没有waiter或有G已经被唤醒或获得锁,不必再去唤醒 // 在饥饿模式,解锁的G直接将锁所有权移交给下一个waiter // 我们不是 if old>>mutexWaiterShift == 0 || // 没有waiter在排队 old&(mutexLocked|mutexWoken|mutexStarving) != 0 {// 处于锁定、唤醒、饥饿状态 return } // Grab the right to wake someone. // 抓住唤醒一个waiter的机会 new = (old - 1<<mutexWaiterShift) | mutexWoken if atomic.CompareAndSwapInt32(&m.state, old, new) { runtime_Semrelease(&m.sema, false) return } old = m.state } } else { // Starving mode: handoff mutex ownership to the next waiter. // Note: mutexLocked is not set, the waiter will set it after wakeup. // But mutex is still considered locked if mutexStarving is set, // so new coming goroutines won't acquire it. // 饥饿模式:移交mutex所有权到下一个waiter // 注意:MutexLocked没有被设置,waiter将会在唤醒后设置它,但如果mutexStarving被设置的情况下 // 需要考虑锁定mutex,因此新到达的G不会获得锁 runtime_Semrelease(&m.sema, true) } }