互斥鎖:沒有讀鎖寫鎖之分,同一時刻,只能有一個gorutine獲取一把鎖
數據結構設計:
type Mutex struct { state int32 // 將一個32位整數拆分為 當前阻塞的goroutine數(30位)|喚醒狀態(1位)|鎖狀態(1位) 的形式,來簡化字段設計 sema uint32 // 信號量 } const ( mutexLocked = 1 << iota // 0001 含義:用最后一位表示當前對象鎖的狀態,0-未鎖住 1-已鎖住 mutexWoken // 0010 含義:用倒數第二位表示當前對象是否被喚醒 0-喚醒 1-未喚醒 mutexWaiterShift = iota // 2 含義:從倒數第二位往前的bit位表示在排隊等待的goroutine數 )
關鍵函數設計:
lock函數:
//獲取鎖,如果鎖已經在使用,則會阻塞一直到鎖可用 func (m *Mutex) Lock() { // m.sate == 0時說明當前對象還沒有被鎖住過,進行原子操賦值作操作設置mutexLocked狀態,CompareAnSwapInt32返回true // m.sate != 1時剛好相反說明對象已被其他goroutine鎖住,不會進行原子賦值操作設置,CopareAndSwapInt32返回false if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) { if race.Enabled { race.Acquire(unsafe.Pointer(m)) } return } awoke := false iter := 0 for { old := m.state // 保存對象當前鎖狀態 new := old | mutexLocked // 保存對象即將被設置成的狀態 if old&mutexLocked != 0 { // 判斷對象是否處於鎖定狀態 if runtime_canSpin(iter) { // 判斷當前goroutine是否可以進入自旋鎖 if !awoke && old&mutexWoken == 0 && old>>mutexWaiterShift != 0 && atomic.CompareAndSwapInt32(&m.state, old, old|mutexWoken) { awoke = true } runtime_doSpin() // 進入自旋鎖后當前goroutine並不掛起,仍然在占用cpu資源,所以重試一定次數后,不會再進入自旋鎖邏輯 iter++ continue } // 更新阻塞goroutine的數量 new = old + 1<<mutexWaiterShift //new = 2 } if awoke { if new&mutexWoken == 0 { panic("sync: inconsistent mutex state") } // 設置喚醒狀態位0 new &^= mutexWoken } if atomic.CompareAndSwapInt32(&m.state, old, new) { if old&mutexLocked == 0 { // 如果對象原本不是鎖定狀態,對象已經獲取到了鎖 break } // 如果對象原本就是鎖定狀態,掛起當前goroutine,進入休眠等待狀態 runtime_Semacquire(&m.sema) awoke = true iter = 0 } } if race.Enabled { race.Acquire(unsafe.Pointer(m)) } }
再來看看unlock函數,終於可以來點輕松的了
func (m *Mutex) Unlock() { if race.Enabled { _ = m.state race.Release(unsafe.Pointer(m)) } // 改變鎖的狀態值 new := atomic.AddInt32(&m.state, -mutexLocked) if (new+mutexLocked)&mutexLocked == 0 { // 如果原來鎖不是鎖定狀態,報錯 panic("sync: unlock of unlocked mutex") } old := new for { // 1. 如果沒有阻塞的goroutine直接返回 // 2. 如果已經被其他goroutine獲取到鎖了直接返回
// 需要說明的是為什么是old&(mutexLocked|mutexWoken)而不是old&mutexLocked
// 首先如果是old&mutexLocked的話,那鎖就沒法釋放了
// 最主要的一點是lock時進入自旋鎖邏輯后,goroutine持有的對象狀態會設置為mutexLocked|mutexWoken
// 這種情況讓再去解鎖后mutexWaiterShift數就會出現不一致情況 if old>>mutexWaiterShift == 0 || old&(mutexLocked|mutexWoken) != 0 { return } // 更新阻塞的goroutine個數 new = (old - 1<<mutexWaiterShift) | mutexWoken if atomic.CompareAndSwapInt32(&m.state, old, new) { // 通知阻塞的goroutine runtime_Semrelease(&m.sema) return } old = m.state } }
總結:
一、互斥效果實現方式
1. 當前goroutine進入自旋鎖邏輯等待中
2. 掛起當前goroutine等待其他goroutine解鎖通知,通過信號量實現
二、鎖數據結構設計十分精簡
goroutine數(30位)|喚醒狀態(1位)|鎖狀態(1位)