1. 程式人生 > >golang RWMutex讀寫鎖分析

golang RWMutex讀寫鎖分析

RWMutex:是基於Mutex實現的讀寫互斥鎖,一個goroutine可以持有多個讀鎖或者一個寫鎖,同一時刻只能持有讀鎖或者寫鎖

資料結構設計:

type RWMutex struct {
    w           Mutex  // 互斥鎖
    writerSem   uint32 // 寫鎖訊號量
    readerSem   uint32 // 讀鎖訊號量
    readerCount int32  // 讀鎖計數器
    readerWait  int32  // 獲取寫鎖時需要等待的讀鎖釋放數量
}
// 獲取寫鎖
func (rw *RWMutex) Lock() {
    if race.Enabled {
        _ = rw.w.state
        race.Disable()
    }
    // 先獲取一把互斥鎖
    rw.w.Lock()
    // 減去最大的讀鎖數量,用0-負數來表示寫鎖已經被獲取
    r := atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders) + rwmutexMaxReaders
    // 設定需要等待釋放的讀鎖數量,如果有,則掛起獲取讀鎖的goroutine
    if r != 0 && atomic.AddInt32(&rw.readerWait, r) != 0 {
        // 掛起,監控寫鎖訊號量
        runtime_Semacquire(&rw.writerSem)
    }
    if race.Enabled {
        race.Enable()
        race.Acquire(unsafe.Pointer(&rw.readerSem))
        race.Acquire(unsafe.Pointer(&rw.writerSem))
    }
}

按順序這裡應該介紹釋放寫鎖的程式碼了,但是由於獲取寫鎖中有很重要的幾個邏輯變數,跟獲取讀鎖時強依賴,所以在這裡先說說獲取讀鎖的邏輯

// 獲取讀鎖
func (rw *RWMutex) RLock() {
    if race.Enabled {
        _ = rw.w.state
        race.Disable()
    }
    
    // 每次獲取讀鎖時,readerCount+1
    // 如果寫鎖已經被獲取,那麼readerCount在-rwmutexMaxReaders與0之間,這時掛起獲取讀鎖的goroutine,
    // 如果寫鎖沒有被獲取,那麼readerCount>=0,然後就沒然後了
    // 這樣通過readerCount的正負就成了讀鎖與寫鎖互斥的判斷條件

    if atomic.AddInt32(&rw.readerCount, 1) < 0 {
        // 掛起,監聽readerSem訊號量
        runtime_Semacquire(&rw.readerSem)
    }
    if race.Enabled {
        race.Enable()
        race.Acquire(unsafe.Pointer(&rw.readerSem))
    }
}
// 釋放讀鎖
func (rw *RWMutex) RUnlock() {
    if race.Enabled {
        _ = rw.w.state
        race.ReleaseMerge(unsafe.Pointer(&rw.writerSem))
        race.Disable()
    }
    // 讀鎖計數器-1
    if r := atomic.AddInt32(&rw.readerCount, -1); r < 0 {
        if r+1 == 0 || r+1 == -rwmutexMaxReaders {
            race.Enable()
            panic("sync: RUnlock of unlocked RWMutex")
        }
        // 如果獲取寫鎖時的goroutine被阻塞,這時需要獲取讀鎖的goroutine全部都釋放,才會被喚醒
        if atomic.AddInt32(&rw.readerWait, -1) == 0 { // 更新需要釋放的讀鎖數量
            // 更新訊號量
            runtime_Semrelease(&rw.writerSem)
        }
    }
    if race.Enabled {
        race.Enable()
    }
}
func (rw *RWMutex) Unlock() {
    if race.Enabled {
        _ = rw.w.state
        race.Release(unsafe.Pointer(&rw.readerSem))
        race.Release(unsafe.Pointer(&rw.writerSem))
        race.Disable()
    }

    // 還原加鎖時減去的那一部分readerCount
    r := atomic.AddInt32(&rw.readerCount, rwmutexMaxReaders)
    if r >= rwmutexMaxReaders {
        race.Enable()
        panic("sync: Unlock of unlocked RWMutex")
    }
    // 喚醒獲取讀鎖期間所有被阻塞的goroutine
    for i := 0; i < int(r); i++ {
        runtime_Semrelease(&rw.readerSem)
    }
    // 釋放互斥鎖資源
    rw.w.Unlock()
    if race.Enabled {
        race.Enable()
    }
}

總結:

讀寫互斥鎖的實現比較有技巧性一些,需要幾點

1. 讀鎖不能阻塞讀鎖,引入readerCount實現

2. 讀鎖需要阻塞寫鎖,直到所以讀鎖都釋放,引入readerSem實現

3. 寫鎖需要阻塞讀鎖,直到所以寫鎖都釋放,引入wirterSem實現

4. 寫鎖需要阻塞寫鎖,引入Metux實現