RWMutex
讀寫鎖相較于互斥鎖有更低的粒度,它允許并發讀,因此在讀操作明顯多于寫操作的場景下能減少鎖競爭的次數,提高程序效率。
type RWMutex struct {w Mutex // held if there are pending writerswriterSem uint32 // semaphore for writers to wait for completing readersreaderSem uint32 // semaphore for readers to wait for completing writersreaderCount int32 // number of pending readersreaderWait int32 // number of departing readers
}
RWMutex
結構體中包含五個字段,分別表示:
w
: 復用互斥鎖writerSem 和 readerSem
: 用于寫等待讀和讀等待寫的信號量readerCount
:readerWait
: 等待寫鎖釋放的讀者數量
讀鎖
RLock
當有 goroutine 寫時,是不允許讀的,這時會把 readerCount
設置為負,這時讀 goroutine 應該被阻塞
func (rw *RWMutex) RLock() {if atomic.AddInt32(&rw.readerCount, 1) < 0 {// 阻塞讀runtime_SemacquireMutex(&rw.readerSem, false, 0)}
}
RUnlock
讀鎖解鎖時只需要將 readerCount - 1
, 如果結果小于零,說明:
- 原來
readerCount == 0 || readerCount == -rwmutexMaxReaders
, 對未加鎖的對象執行了解鎖操作 - 原來
readerCount < 0
, 有正在執行的寫操作
func (rw *RWMutex) RUnlock() {if r := atomic.AddInt32(&rw.readerCount, -1); r < 0 {// Outlined slow-path to allow the fast-path to be inlinedrw.rUnlockSlow(r)}
}func (rw *RWMutex) rUnlockSlow(r int32) {if r+1 == 0 || r+1 == -rwmutexMaxReaders {race.Enable()throw("sync: RUnlock of unlocked RWMutex")}// 所有讀操作結束后,觸發寫的寫信號量if atomic.AddInt32(&rw.readerWait, -1) == 0 {// The last reader unblocks the writer.runtime_Semrelease(&rw.writerSem, false, 1)}
}
寫鎖
Lock
func (rw *RWMutex) Lock() {// 獲取互斥寫鎖rw.w.Lock()// 阻塞讀r := atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders) + rwmutexMaxReaders// 如果有人在讀,需要等待讀鎖釋放if r != 0 && atomic.AddInt32(&rw.readerWait, r) != 0 {// 阻塞等待讀鎖釋放runtime_SemacquireMutex(&rw.writerSem, false, 0)}
}
Lock 會先通過互斥鎖阻塞寫操作,然后禁止讀鎖獲取,等待已經持有讀鎖的 goroutine 釋放讀鎖,這樣可以避免連續的寫操作使讀陷入饑餓。
Unlock
func (rw *RWMutex) Unlock() {// 重新允許讀鎖獲取r := atomic.AddInt32(&rw.readerCount, rwmutexMaxReaders)if r >= rwmutexMaxReaders {race.Enable()throw("sync: Unlock of unlocked RWMutex")}// 觸發等待中的寫鎖的信號量for i := 0; i < int(r); i++ {runtime_Semrelease(&rw.readerSem, false, 0)}// 互斥鎖解鎖rw.w.Unlock()
}
總結
在極端情況下:
- 如果完全沒有寫,讀鎖加鎖只是將 readerCount 加一,解鎖只是將其減一,不存在鎖競爭。
- 如果只有寫,加鎖和解鎖都比互斥鎖多了一個對 readerCount 取反操作
在一般情況下,讀鎖在獲取鎖前會檢查 readerCount, 如果為負,說明有人在寫,則進入阻塞狀態,等待 readerSem
的信號,寫鎖獲取鎖在得到互斥鎖后會先設置 readerCount 為負,阻止新的讀者獲取讀鎖,然后需要等待所有已經持有讀鎖的 goroutine 釋放讀鎖,這里使用的是 readerWait
,當 readerCount 為負時,如果讀鎖被釋放,該量就會減一,當 readerWait == 0
時,則說明所有在寫鎖獲取之前獲得的讀鎖都被釋放了,最后一個釋放的讀鎖會通過 writerSem
通知寫對象。
寫鎖釋放時,需要通過 readerSem
信號觸發所有阻塞中的寫對象。