談談自己對 GO 的 RWMutex 的理解

【導讀】go 中 RWMutex 和 Mutex 的區別在哪裏?什麼場景下使用哪種鎖?本文做了詳細介紹。

RWMutex 核心還是基於 Mutex 的,RWMutex 的特性就是支持併發讀。適用於讀多寫少的場景。

RWMutex 的定義

type RWMutex struct {
    w           Mutex  // 互斥鎖
    writerSem   uint32 // 寫鎖用的信號量
    readerSem   uint32 // 讀鎖用的信號量
    readerCount int32  // 當前正在執行讀操作的 goroutine 數量
    readerWait  int32  // 獲取寫鎖時,當前還持有讀鎖的 goroutine 數量
}

const rwmutexMaxReaders = 1 << 30

RWMutex.Lock()

func (rw *RWMutex) Lock() {
    // 首先調用 Mutex 的 Lock 方法獲取到鎖
    rw.w.Lock()
    
    // 把 readerCount 改成負數,這樣後續的讀操作就會被阻塞
    // r 就是當前正在執行讀操作的 goroutine 數量 
    r := atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders) + rwmutexMaxReaders
    
    // 如果當前有正在執行讀操作的 goroutine
    // 把 r 賦值給 readerWait
    if r != 0 && atomic.AddInt32(&rw.readerWait, r) != 0 {
    
        // 獲取寫鎖的 goroutine 進入休眠,等待被喚醒
        runtime_SemacquireMutex(&rw.writerSem, false, 0)
    }
}

RWMutex.Unlock()

func (rw *RWMutex) Unlock() {

    // 把 readerCount 改成正數,這樣後續讀操作就不會被阻塞了
    r := atomic.AddInt32(&rw.readerCount, rwmutexMaxReaders)
    ...
    
    // 手動喚醒之前被寫鎖阻塞的讀操作 goroutine
    for i := 0; i < int(r); i++ {
        runtime_Semrelease(&rw.readerSem, false, 0)
    }
    
    // 釋放互斥鎖,其他寫鎖就可以競爭互斥鎖了
    rw.w.Unlock()
}

RWMutex.RLock()

func (rw *RWMutex) RLock() {
    ...
    
    // readerCount + 1
    if atomic.AddInt32(&rw.readerCount, 1) < 0 {
    
        // 小於 0,說明有其他 goroutine 獲取了寫鎖,當前 goroutine 等待
        runtime_SemacquireMutex(&rw.readerSem, false, 0)
    }
    ...
}

RWMutex.RUnlock()

func (rw *RWMutex) RUnlock() {
    ...
    // readerCount - 1
    // readerCount < 0, 說明其他 gouroutine 獲取了寫鎖,正在等待還持有讀鎖的 goroutine 釋放讀鎖
    // readerCount >= 0, 說明沒有寫鎖被阻塞,直接返回就行了
    if r := atomic.AddInt32(&rw.readerCount, -1); r < 0 {
    
        // 釋放讀鎖
        rw.rUnlockSlow(r)
    }
    ...
}

func (rw *RWMutex) rUnlockSlow(r int32) {
    ...
    
    // readerWait - 1
    // 判斷當前 goroutine 是不是最後一個釋放讀鎖
    if atomic.AddInt32(&rw.readerWait, -1) == 0 {
    
        // 喚醒寫鎖
        runtime_Semrelease(&rw.writerSem, false, 1)
    }
}

總結

獲取讀鎖的流程
  1. readerCount + 1

  2. 以 readerCount<0, 判斷是否被寫鎖阻塞,是的話,當前 goroutine 進入休眠

釋放讀鎖的流程
  1. readerCount - 1

  2. 以 readerCount<0, 判斷是否有寫鎖

  3. 沒有寫鎖的話,直接返回

  4. 有寫鎖的話,調用 rUnlockSlow 方法,readerWait - 1

  5. 如果 readerWait == 0, 說明當前 goroutine 是寫鎖等待的最後一個讀鎖 goroutine,需要喚醒寫鎖 goroutine

獲取寫鎖的流程
  1. 先獲取互斥鎖

  2. readerCount - rwmutexMaxReaders,後續讀操作全部阻塞

  3. readerWait += readerCount,把當前正在執行讀操作的數量加到 readerWait 上

  4. 如果 readerWait != 0 ,說明當前還有其他 goroutine 持有讀鎖,當前 goroutine 進入睡眠,等待喚醒

釋放寫鎖流程
  1. readerCount + rwmutexMaxReaders, 後續讀鎖不會阻塞

  2. readerCount 代表之前被寫鎖阻塞的讀鎖 goroutine 個數,喚醒 readerCount 個讀鎖 goroutine

  3. 最後釋放互斥鎖

最後

RWMutex 相對 Mutex,增加了讀鎖的控制,就代碼邏輯複雜度而言,RWMutex 比 Mutex 要簡單很多,對 Mutex 的流程熟悉的話,很快就能掌握 RWMutex 的原理

轉自:iuoui

segmentfault.com/a/1190000023372322

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/VsV_8urVXxqRH3ul_ROh0A