golang RWMutex讀寫鎖分析

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

數據結構設計:ui

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))
    }
}

按順序這裏應該介紹釋放寫鎖的代碼了,可是因爲獲取寫鎖中有很重要的幾個邏輯變量,跟獲取讀鎖時強依賴,因此在這裏先說說獲取讀鎖的邏輯atom

// 獲取讀鎖
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()
    }
}

總結:spa

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

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

2. 讀鎖須要阻塞寫鎖,直到因此讀鎖都釋放,引入readerSem實現blog

3. 寫鎖須要阻塞讀鎖,直到因此寫鎖都釋放,引入wirterSem實現資源

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

相關文章
相關標籤/搜索