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

// Happens-before relationships are indicated to the race detector via:
// - Unlock -> Lock: readerSem
// - Unlock -> RLock: readerSem
// - RUnlock -> Lock: writerSem
//
// happends-before 用于描述時(shí)間發(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,說(shuō)明有一個(gè)寫(xiě)者拿著鎖了, 這邊需要等著
if rw.readerCount.Add(1) < 0 {
// A writer is pending, wait for it.
// 在讀寫(xiě)鎖的情況下 執(zhí)行鎖的信號(hào)量
// 實(shí)際得等待操作, 調(diào)用底層代碼, 等寫(xiě)者釋放鎖
runtime_SemacquireRWMutexR(&rw.readerSem, false, 0)
}
if race.Enabled {
race.Enable()
race.Acquire(unsafe.Pointer(&rw.readerSem))
}
}
race.Enabled : 競(jìng)態(tài)檢測(cè), 是go運(yùn)行時(shí)提供的工具, 用于檢測(cè)并發(fā)程序中的數(shù)據(jù)競(jìng)爭(zhēng)問(wèn)題,
使用 go run -race main.go 可以檢測(cè), 然后輸出報(bào)告。
后面競(jìng)態(tài)檢測(cè)代碼 不說(shuō)明
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() {
... // 競(jìng)態(tài)規(guī)則邏輯
// readerCount 用于記錄當(dāng)前持有讀鎖的讀者數(shù)量。如果讀者計(jì)數(shù)減為負(fù)數(shù),說(shuō)明存在寫(xiě)者正在等待讀鎖釋放
if r := rw.readerCount.Add(-1); r < 0 {
// Outlined slow-path to allow the fast-path to be inlined
rw.rUnlockSlow(r)
}
... // 競(jìng)態(tài)規(guī)則邏輯
}
func (rw *RWMutex) rUnlockSlow(r int32) {
... // 競(jìng)態(tài)規(guī)則邏輯
// A writer is pending.
// 減少讀者等待計(jì)數(shù)。readerWait 記錄正在等待讀鎖釋放的讀者數(shù)量。如果讀者等待計(jì)數(shù)減為零,
// 說(shuō)明最后一個(gè)讀者已經(jīng)釋放了讀鎖,可以喚醒等待的寫(xiě)者
if rw.readerWait.Add(-1) == 0 {
// The last reader unblocks the writer.
// 釋放寫(xiě)者的信號(hào)量
runtime_Semrelease(&rw.writerSem, false, 1)
}
}

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

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

