go中sync.RWMutex的源碼解讀
簡介
簡述sync包中讀寫鎖的源碼。 (go -version 1.21)
讀寫鎖(RWMutex)是一種并發(fā)控制機(jī)制,用于在多個 goroutine 之間對共享資源進(jìn)行讀寫操作。它提供了兩種鎖定方式:讀鎖和寫鎖。
讀鎖(RLock):多個 goroutine 可以同時持有讀鎖,而不會阻塞彼此。只有當(dāng)沒有寫鎖被持有時,讀鎖才會被授予。這樣可以實現(xiàn)多個 goroutine 并發(fā)地讀取共享資源,提高程序性能。
寫鎖(Lock):寫鎖是排它的,當(dāng)某個 goroutine 持有寫鎖時,其他所有 goroutine 都無法獲得讀鎖或?qū)戞i。這是為了確保在寫入共享資源時,沒有其他 goroutine 在讀或?qū)懺撡Y源。
寫鎖是排他性的 :
(假設(shè)寫鎖不是排他性的, 新的讀鎖可以被獲取) 反證:(多個寫鎖的情況下就不聊了)
現(xiàn)在有 一個goroutine1 獲取讀鎖; 一個 goroutine2 獲取寫鎖,但沒有寫鎖被其他goroutine持有
然后有個goroutine3 想獲取讀鎖,在假設(shè)的條件下 goroutine3 就會獲取到讀鎖, 會導(dǎo)致goroutine2 無法獲取寫鎖, 極端情況下會導(dǎo)致goroutine2 一直獲取不到寫鎖 ---- 寫鎖饑餓
所以 為了讀寫公平, 還是把寫鎖優(yōu)先級提高, 在寫鎖的情況下, 新的讀鎖無法被獲取。
源碼
結(jié)構(gòu)
// RWMutex 結(jié)構(gòu)體包含了用于讀寫互斥鎖的各種狀態(tài)和信號量。在 RWMutex 中,多個 goroutine 可以同時持有讀鎖,
// 但只有一個 goroutine 可以持有寫鎖。RWMutex 的實現(xiàn)確保了在寫鎖等待的情況下,新的讀鎖無法被獲取,
// 從而防止了讀鎖長時間等待
type RWMutex struct {
w Mutex // 用于寫鎖的互斥鎖
writerSem uint32 // 寫鎖的信號量,用于等待讀鎖完成
readerSem uint32 // 讀鎖的信號量,用于等待寫鎖完成
readerCount atomic.Int32 // 讀者持有讀者鎖的數(shù)量
readerWait atomic.Int32 // 寫者等待讀鎖的數(shù)量
}
// readerCount
// 當(dāng)讀者加鎖時, readerCount +1
// 當(dāng)讀者解鎖時, readerCount -1
// 當(dāng) readerCount 大于 0 時,表示有讀者持有讀鎖。
// 如果 readerCount 等于 0,則表示當(dāng)前沒有讀者持有讀鎖。
// 當(dāng) readerCount 等于 0 時,其他寫者可以嘗試獲取寫鎖
// 當(dāng) readerCount < 0 , 說明有寫者在等待, 讀者需要等待寫者釋放寫鎖
// readerWait(寫者等待讀鎖的數(shù)量):
// 當(dāng)寫者嘗試獲取寫鎖,但當(dāng)前有讀者持有讀鎖時,寫者會被阻塞,并且 readerWait 會增加。
// 當(dāng)讀者釋放讀鎖時,如果有寫者在等待讀鎖,readerWait 會減少,并且可能喚醒等待的寫者
// readerCount > 0:表示有讀者持有讀鎖。
// readerCount == 0 且 readerWait > 0:表示有寫者在等待讀鎖,阻塞中。
// readerCount == 0 且 readerWait == 0:表示當(dāng)前沒有讀者持有讀鎖,且沒有寫者在等待讀鎖。此時其他寫者可以嘗試獲取寫鎖。
// 這樣的設(shè)計是為了在寫者等待讀鎖時,不允許新的讀者加入,以確保寫者獲得更公平的機(jī)會。
RLock

// Happens-before relationships are indicated to the race detector via:
// - Unlock -> Lock: readerSem
// - Unlock -> RLock: readerSem
// - RUnlock -> Lock: writerSem
//
// happends-before 用于描述時間發(fā)生的順序,在 這里 (代碼中的注釋)
// Unlock 事件發(fā)生之前(happens-before)的 Lock 事件。
// Unlock 事件發(fā)生之前(happens-before)的 RLock 事件。
// RUnlock 事件發(fā)生之前(happens-before)的 Lock 事件
func (rw *RWMutex) RLock() {
if race.Enabled {
_ = rw.w.state
race.Disable()
}
// 它將 readerCount 增加 1。如果結(jié)果小于 0,說明有一個寫者拿著鎖了, 這邊需要等著
if rw.readerCount.Add(1) < 0 {
// A writer is pending, wait for it.
// 在讀寫鎖的情況下 執(zhí)行鎖的信號量
// 實際得等待操作, 調(diào)用底層代碼, 等寫者釋放鎖
runtime_SemacquireRWMutexR(&rw.readerSem, false, 0)
}
if race.Enabled {
race.Enable()
race.Acquire(unsafe.Pointer(&rw.readerSem))
}
}
race.Enabled : 競態(tài)檢測, 是go運(yùn)行時提供的工具, 用于檢測并發(fā)程序中的數(shù)據(jù)競爭問題,
使用 go run -race main.go 可以檢測, 然后輸出報告。
后面競態(tài)檢測代碼 不說明
RUnlock

// RUnlock undoes a single RLock call;
// it does not affect other simultaneous readers.
// It is a run-time error if rw is not locked for reading
// on entry to RUnlock.
func (rw *RWMutex) RUnlock() {
... // 競態(tài)規(guī)則邏輯
// readerCount 用于記錄當(dāng)前持有讀鎖的讀者數(shù)量。如果讀者計數(shù)減為負(fù)數(shù),說明存在寫者正在等待讀鎖釋放
if r := rw.readerCount.Add(-1); r < 0 {
// Outlined slow-path to allow the fast-path to be inlined
rw.rUnlockSlow(r)
}
... // 競態(tài)規(guī)則邏輯
}
func (rw *RWMutex) rUnlockSlow(r int32) {
... // 競態(tài)規(guī)則邏輯
// A writer is pending.
// 減少讀者等待計數(shù)。readerWait 記錄正在等待讀鎖釋放的讀者數(shù)量。如果讀者等待計數(shù)減為零,
// 說明最后一個讀者已經(jīng)釋放了讀鎖,可以喚醒等待的寫者
if rw.readerWait.Add(-1) == 0 {
// The last reader unblocks the writer.
// 釋放寫者的信號量
runtime_Semrelease(&rw.writerSem, false, 1)
}
}

func (rw *RWMutex) Lock() {
... // 競態(tài)規(guī)則邏輯
// First, resolve competition with other writers.
// 獲取寫鎖,解決與其他寫者的競爭
rw.w.Lock()
// Announce to readers there is a pending writer.
// 將 readerCount 減去 rwmutexMaxReaders,然后再加上 rwmutexMaxReaders,目的是將 readerCount 設(shè)置為負(fù)數(shù),
// 表示有一個寫者正在等待寫鎖。這會通過 readerWait 記錄下來,并用于通知讀者和寫者。
// 這邊得 rw.readerCount 是一個負(fù)數(shù), 在RLock 中有個判斷 rw.readerCount <0 ,
// 這一段就是實現(xiàn)了寫者優(yōu)先, 不管當(dāng)前有沒有讀者拿著讀鎖, 接下來拿鎖的讀鎖, 統(tǒng)統(tǒng)排我后面,不能影響我(寫者), 等我(寫者)處理完了再說
r := rw.readerCount.Add(-rwmutexMaxReaders) + rwmutexMaxReaders
// Wait for active readers.
// 如果有活躍的讀者,且將 readerWait 增加 r 后不為零,說明有讀者正在等待讀鎖釋放
if r != 0 && rw.readerWait.Add(r) != 0 {
// 獲取寫鎖。
// 它不是馬上就能獲取寫鎖,而是可能會被阻塞,需要等待寫鎖的釋放
// 當(dāng)寫者執(zhí)行這個操作時,如果當(dāng)前沒有其他寫者或讀者持有鎖,那么它會成功獲取寫鎖,否則它會被阻塞,直到?jīng)]有其他寫者或讀者持有鎖
runtime_SemacquireRWMutex(&rw.writerSem, false, 0)
}
... // 競態(tài)規(guī)則邏輯
}
Unlock

// arrange for another goroutine to RUnlock (Unlock) it.
func (rw *RWMutex) Unlock() {
... // 競態(tài)規(guī)則邏輯
// Announce to readers there is no active writer.
// 將 readerCount 增加 rwmutexMaxReaders,用于通知活躍的讀者,寫者已經(jīng)釋放了寫鎖
// 我( 寫者)處理完了, 把 rw.readerCount 加回來了, 當(dāng)前寫鎖寫完了, 剛剛我(寫者)拿鎖以后 申請的讀鎖們, 可以喚醒了
r := rw.readerCount.Add(rwmutexMaxReaders)
... // 競態(tài)規(guī)則邏輯
// Unblock blocked readers, if any.
// 遍歷并逐個釋放等待的讀者,通過 runtime_Semrelease 信號量操作通知它們。
for i := 0; i < int(r); i++ {
runtime_Semrelease(&rw.readerSem, false, 0)
}
// Allow other writers to proceed.
rw.w.Unlock()
... // 競態(tài)規(guī)則邏輯
}
go 運(yùn)行時方法
1、runtime_SemacquireRWMutexR(&rw.readerSem, false, 0):
這個方法用于獲取讀鎖。
&rw.readerSem 是一個信號量,表示等待讀鎖的讀者隊列。
false 表示不以 LIFO(LastIn, First Out)模式進(jìn)行等待隊列的管理。 、
0 表示無超時。
2、runtime_SemacquireRWMutex(&rw.writerSem, false, 0):
這個方法用于獲取寫鎖。
&rw.writerSem 是一個信號量,表示等待寫鎖的寫者隊列。
false 表示不以 LIFO模式進(jìn)行等待隊列的管理。
0 表示無超時。
3、runtime_Semrelease(&rw.writerSem, false, 1):
這個方法用于釋放寫鎖。
&rw.writerSem 是寫鎖等待隊列的信號量。
false 表示不以 LIFO 模式進(jìn)行等待隊列的管理。
1 表示釋放一個寫者,通知等待的寫者。
4、runtime_Semrelease(&rw.readerSem, false, 0):
這個方法用于釋放讀鎖。
&rw.readerSem 是讀鎖等待隊列的信號量。
false 表示不以 LIFO 模式進(jìn)行等待隊列的管理。
0表示釋放一個讀者,通知等待的讀者
到此這篇關(guān)于go中sync.RWMutex的源碼解讀的文章就介紹到這了,更多相關(guān)go sync.RWMutex內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
執(zhí)行g(shù)o?build報錯go:?go.mod?file?not?found?in?current?dir
本文主要為大家介紹了執(zhí)行g(shù)o build報錯go:?go.mod?file?not?found?in?current?directory?or?any?parent?directory解決分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06
Golang語言如何讀取http.Request中body的內(nèi)容
這篇文章主要介紹了Golang語言如何讀取http.Request中body的內(nèi)容問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-03-03
一文告訴你大神是如何學(xué)習(xí)Go語言之make和new
當(dāng)我們想要在 Go 語言中初始化一個結(jié)構(gòu)時,其實會使用到兩個完全不同的關(guān)鍵字,也就是 make 和 new,同時出現(xiàn)兩個用于『初始化』的關(guān)鍵字對于初學(xué)者來說可能會感到非常困惑,不過它們兩者有著卻完全不同的作用,本文就和大家詳細(xì)講講2023-02-02

