sync.Mutex
go
复制代码
type Mutex struct {
_ noCopy
mu isync.Mutex
}
// isync "internal/sync"
type Mutex struct {
state int32 // 一个 int32 里用位表示「是否加锁、是否唤醒中、是否饥饿、等待者个数」
sema uint32 // 信号量,用来在抢不到锁时 sleep / 被 Unlock 时 wake 一个 waiter。
}
const (
mutexLocked = 1 << 0 // 1:当前有人持有锁
mutexWoken = 1 << 1 // 2:已有 goroutine 被唤醒(或正在自旋),Unlock 不必再唤醒别人
mutexStarving = 1 << 2 // 4:饥饿模式
mutexWaiterShift = 3 // 高 29 位:等待者数量
)
Lock
快速路径 :当前 state == 0(没人持锁、没人等),CAS 成 mutexLocked 就拿到锁,直接 return。
否则 进 lockSlow() ,里面做自旋、排队、饥饿切换等。
go
复制代码
func (m *Mutex) Lock() {
if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {
...
return
}
m.lockSlow()
}
go
复制代码
func (m *Mutex) lockSlow() {
...
for {
// 当前有人持锁且不是饥饿模式,开始自旋
if old&(mutexLocked|mutexStarving) == mutexLocked && runtime_canSpin(iter) {
if !awoke && old&mutexWoken == 0 && old>>mutexWaiterShift != 0 &&
atomic.CompareAndSwapInt32(&m.state, old, old|mutexWoken) {
//别去叫醒队列里的兄弟了,我已经在这等着抢了
awoke = true
}
runtime_doSpin()
iter++
old = m.state
continue
}
new := old
if old&mutexStarving == 0 {
new |= mutexLocked // 只要不是饥饿模式,就尝试抢锁
}
if old&(mutexLocked|mutexStarving) != 0 {
new += 1 << mutexWaiterShift // 如果锁被占或在饥饿模式,自己乖乖去排队,等待者+1
}
if starving && old&mutexLocked != 0 {
new |= mutexStarving // 如果我已经等了超过 1ms,就把锁标记为饥饿模式
}
...
if atomic.CompareAndSwapInt32(&m.state, old, new) {
if old&(mutexLocked|mutexStarving) == 0 {
break // 成功抢到锁,大功告成
}
// 抢不到,调用信号量进入休眠
queueLifo := waitStartTime != 0
if waitStartTime == 0 {
waitStartTime = runtime_nanotime()
}
runtime_SemacquireMutex(&m.sema, queueLifo, 2)
// 被唤醒后,检查自己等了多久,如果太久,开启饥饿标记
starving = starving || runtime_nanotime()-waitStartTime > starvationThresholdNs
old = m.state
// 饥饿模式的处理
if old&mutexStarving != 0 {
// 此时所有权直接从上一个 Unlock 的人手里传到了这个被唤醒的人手里
...
delta := int32(mutexLocked - 1<<mutexWaiterShift)
if !starving || old>>mutexWaiterShift == 1 {
// 如果我是最后一个人,或者我等的没那么久了,退出饥饿模式
delta -= mutexStarving
}
atomic.AddInt32(&m.state, delta)
break
}
awoke = true
iter = 0
} else {
old = m.state
}
}
if race.Enabled {
race.Acquire(unsafe.Pointer(m))
}
}
TryLock
若已经加锁 或处于饥饿 (饥饿时锁要交给 waiter,不能给 TryLock),直接返回 false。
否则 CAS 把 mutexLocked 置 1,成功返回 true,失败返回 false。不排队、不自旋 。
go
复制代码
func (m *Mutex) TryLock() bool {
old := m.state
if old&(mutexLocked|mutexStarving) != 0 {
return false
}
if !atomic.CompareAndSwapInt32(&m.state, old, old|mutexLocked) {
return false
}
if race.Enabled {
race.Acquire(unsafe.Pointer(m))
}
return true
}
UnLock
go
复制代码
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 != 0 {
// Outlined slow path to allow inlining the fast path.
// To hide unlockSlow during tracing we skip one extra frame when tracing GoUnblock.
m.unlockSlow(new)
}
}
检查 :若 (new+mutexLocked)&mutexLocked == 0,说明本来就没锁,fatal("unlock of unlocked mutex") 。
非饥饿 :
若没有 waiter,或已经有别人把锁/唤醒拿走了,直接 return。
否则 CAS:waiter 数减 1、置上 mutexWoken ,成功则 runtime_Semrelease(&m.sema, ...) 唤醒一个 waiter。
饥饿 :
go
复制代码
func (m *Mutex) unlockSlow(new int32) {
if (new+mutexLocked)&mutexLocked == 0 {
fatal("sync: unlock of unlocked mutex")
}
if new&mutexStarving == 0 {
old := new
for {
if old>>mutexWaiterShift == 0 || old&(mutexLocked|mutexWoken|mutexStarving) != 0 {
return
}
// Grab the right to wake someone.
new = (old - 1<<mutexWaiterShift) | mutexWoken
if atomic.CompareAndSwapInt32(&m.state, old, new) {
runtime_Semrelease(&m.sema, false, 2)
return
}
old = m.state
}
} else {
runtime_Semrelease(&m.sema, true, 2)
}
}