golang RWMutex讀寫鎖分析
阿新 • • 發佈:2019-01-07
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實現