go中sync.RWMutex的源碼解讀
簡介
簡述sync包中讀寫鎖的源碼。 (go -version 1.21)
讀寫鎖(RWMutex)是一種并發(fā)控制機(jī)制,用于在多個(gè) goroutine 之間對(duì)共享資源進(jìn)行讀寫操作。它提供了兩種鎖定方式:讀鎖和寫鎖。
讀鎖(RLock):多個(gè) goroutine 可以同時(shí)持有讀鎖,而不會(huì)阻塞彼此。只有當(dāng)沒有寫鎖被持有時(shí),讀鎖才會(huì)被授予。這樣可以實(shí)現(xiàn)多個(gè) goroutine 并發(fā)地讀取共享資源,提高程序性能。
寫鎖(Lock):寫鎖是排它的,當(dāng)某個(gè) goroutine 持有寫鎖時(shí),其他所有 goroutine 都無法獲得讀鎖或?qū)戞i。這是為了確保在寫入共享資源時(shí),沒有其他 goroutine 在讀或?qū)懺撡Y源。
寫鎖是排他性的 :
(假設(shè)寫鎖不是排他性的, 新的讀鎖可以被獲取) 反證:(多個(gè)寫鎖的情況下就不聊了)
現(xiàn)在有 一個(gè)goroutine1 獲取讀鎖; 一個(gè) goroutine2 獲取寫鎖,但沒有寫鎖被其他goroutine持有
然后有個(gè)goroutine3 想獲取讀鎖,在假設(shè)的條件下 goroutine3 就會(huì)獲取到讀鎖, 會(huì)導(dǎo)致goroutine2 無法獲取寫鎖, 極端情況下會(huì)導(dǎo)致goroutine2 一直獲取不到寫鎖 ---- 寫鎖饑餓
所以 為了讀寫公平, 還是把寫鎖優(yōu)先級(jí)提高, 在寫鎖的情況下, 新的讀鎖無法被獲取。
源碼
結(jié)構(gòu)
// RWMutex 結(jié)構(gòu)體包含了用于讀寫互斥鎖的各種狀態(tài)和信號(hào)量。在 RWMutex 中,多個(gè) goroutine 可以同時(shí)持有讀鎖, // 但只有一個(gè) goroutine 可以持有寫鎖。RWMutex 的實(shí)現(xiàn)確保了在寫鎖等待的情況下,新的讀鎖無法被獲取, // 從而防止了讀鎖長時(shí)間等待 type RWMutex struct { w Mutex // 用于寫鎖的互斥鎖 writerSem uint32 // 寫鎖的信號(hào)量,用于等待讀鎖完成 readerSem uint32 // 讀鎖的信號(hào)量,用于等待寫鎖完成 readerCount atomic.Int32 // 讀者持有讀者鎖的數(shù)量 readerWait atomic.Int32 // 寫者等待讀鎖的數(shù)量 } // readerCount // 當(dāng)讀者加鎖時(shí), readerCount +1 // 當(dāng)讀者解鎖時(shí), readerCount -1 // 當(dāng) readerCount 大于 0 時(shí),表示有讀者持有讀鎖。 // 如果 readerCount 等于 0,則表示當(dāng)前沒有讀者持有讀鎖。 // 當(dāng) readerCount 等于 0 時(shí),其他寫者可以嘗試獲取寫鎖 // 當(dāng) readerCount < 0 , 說明有寫者在等待, 讀者需要等待寫者釋放寫鎖 // readerWait(寫者等待讀鎖的數(shù)量): // 當(dāng)寫者嘗試獲取寫鎖,但當(dāng)前有讀者持有讀鎖時(shí),寫者會(huì)被阻塞,并且 readerWait 會(huì)增加。 // 當(dāng)讀者釋放讀鎖時(shí),如果有寫者在等待讀鎖,readerWait 會(huì)減少,并且可能喚醒等待的寫者 // readerCount > 0:表示有讀者持有讀鎖。 // readerCount == 0 且 readerWait > 0:表示有寫者在等待讀鎖,阻塞中。 // readerCount == 0 且 readerWait == 0:表示當(dāng)前沒有讀者持有讀鎖,且沒有寫者在等待讀鎖。此時(shí)其他寫者可以嘗試獲取寫鎖。 // 這樣的設(shè)計(jì)是為了在寫者等待讀鎖時(shí),不允許新的讀者加入,以確保寫者獲得更公平的機(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,說明有一個(gè)寫者拿著鎖了, 這邊需要等著 if rw.readerCount.Add(1) < 0 { // A writer is pending, wait for it. // 在讀寫鎖的情況下 執(zhí)行鎖的信號(hào)量 // 實(shí)際得等待操作, 調(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)行時(shí)提供的工具, 用于檢測并發(fā)程序中的數(shù)據(jù)競爭問題,
使用 go run -race main.go 可以檢測, 然后輸出報(bào)告。
后面競態(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ù)量。如果讀者計(jì)數(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. // 減少讀者等待計(jì)數(shù)。readerWait 記錄正在等待讀鎖釋放的讀者數(shù)量。如果讀者等待計(jì)數(shù)減為零, // 說明最后一個(gè)讀者已經(jīng)釋放了讀鎖,可以喚醒等待的寫者 if rw.readerWait.Add(-1) == 0 { // The last reader unblocks the writer. // 釋放寫者的信號(hào)量 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ù), // 表示有一個(gè)寫者正在等待寫鎖。這會(huì)通過 readerWait 記錄下來,并用于通知讀者和寫者。 // 這邊得 rw.readerCount 是一個(gè)負(fù)數(shù), 在RLock 中有個(gè)判斷 rw.readerCount <0 , // 這一段就是實(shí)現(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 { // 獲取寫鎖。 // 它不是馬上就能獲取寫鎖,而是可能會(huì)被阻塞,需要等待寫鎖的釋放 // 當(dāng)寫者執(zhí)行這個(gè)操作時(shí),如果當(dāng)前沒有其他寫者或讀者持有鎖,那么它會(huì)成功獲取寫鎖,否則它會(huì)被阻塞,直到?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)前寫鎖寫完了, 剛剛我(寫者)拿鎖以后 申請(qǐng)的讀鎖們, 可以喚醒了 r := rw.readerCount.Add(rwmutexMaxReaders) ... // 競態(tài)規(guī)則邏輯 // Unblock blocked readers, if any. // 遍歷并逐個(gè)釋放等待的讀者,通過 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() ... // 競態(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 表示無超時(shí)。
2、runtime_SemacquireRWMutex(&rw.writerSem, false, 0):
這個(gè)方法用于獲取寫鎖。
&rw.writerSem 是一個(gè)信號(hào)量,表示等待寫鎖的寫者隊(duì)列。
false 表示不以 LIFO模式進(jìn)行等待隊(duì)列的管理。
0 表示無超時(shí)。
3、runtime_Semrelease(&rw.writerSem, false, 1):
這個(gè)方法用于釋放寫鎖。
&rw.writerSem 是寫鎖等待隊(duì)列的信號(hào)量。
false 表示不以 LIFO 模式進(jìn)行等待隊(duì)列的管理。
1 表示釋放一個(gè)寫者,通知等待的寫者。
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語言實(shí)現(xiàn)爬樓梯算法的思路詳解
這篇文章主要介紹了Go和PHP 實(shí)現(xiàn)爬樓梯算法,本文通過動(dòng)態(tài)規(guī)劃和斐波那契數(shù)列兩種解決思路給大家講解的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-05-05Go并發(fā)編程之sync.Once使用實(shí)例詳解
sync.Once使用起來很簡單, 下面是一個(gè)簡單的使用案例,本文通過實(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-06Golang語言如何讀取http.Request中body的內(nèi)容
這篇文章主要介紹了Golang語言如何讀取http.Request中body的內(nèi)容問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-03-03一文告訴你大神是如何學(xué)習(xí)Go語言之make和new
當(dāng)我們想要在 Go 語言中初始化一個(gè)結(jié)構(gòu)時(shí),其實(shí)會(huì)使用到兩個(gè)完全不同的關(guān)鍵字,也就是 make 和 new,同時(shí)出現(xiàn)兩個(gè)用于『初始化』的關(guān)鍵字對(duì)于初學(xué)者來說可能會(huì)感到非常困惑,不過它們兩者有著卻完全不同的作用,本文就和大家詳細(xì)講講2023-02-02