golang mutex互斥鎖分析

互斥鎖:沒有讀鎖寫鎖之分,同一時刻,只能有一個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數
)

關鍵函數設計:ui

lock函數:atom

//獲取鎖,若是鎖已經在使用,則會阻塞一直到鎖可用
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函數,終於能夠來點輕鬆的了spa

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、互斥效果實現方式code

  1. 當前goroutine進入自旋鎖邏輯等待中對象

  2. 掛起當前goroutine等待其餘goroutine解鎖通知,經過信號量實現blog

2、鎖數據結構設計十分精簡資源

     goroutine數(30位)|喚醒狀態(1位)|鎖狀態(1位)

相關文章
相關標籤/搜索