go中sync.RWMutex的源碼解讀
簡介
簡述sync包中讀寫鎖的源碼。 (go -version 1.21)
讀寫鎖(RWMutex)是一種并發(fā)控制機制,用于在多個 goroutine 之間對共享資源進行讀寫操作。它提供了兩種鎖定方式:讀鎖和寫鎖。
讀鎖(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è)計是為了在寫者等待讀鎖時,不允許新的讀者加入,以確保寫者獲得更公平的機會。
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運行時提供的工具, 用于檢測并發(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 運行時方法
1、runtime_SemacquireRWMutexR(&rw.readerSem, false, 0):
這個方法用于獲取讀鎖。
&rw.readerSem 是一個信號量,表示等待讀鎖的讀者隊列。
false 表示不以 LIFO(LastIn, First Out)模式進行等待隊列的管理。 、
0 表示無超時。
2、runtime_SemacquireRWMutex(&rw.writerSem, false, 0):
這個方法用于獲取寫鎖。
&rw.writerSem 是一個信號量,表示等待寫鎖的寫者隊列。
false 表示不以 LIFO模式進行等待隊列的管理。
0 表示無超時。
3、runtime_Semrelease(&rw.writerSem, false, 1):
這個方法用于釋放寫鎖。
&rw.writerSem 是寫鎖等待隊列的信號量。
false 表示不以 LIFO 模式進行等待隊列的管理。
1 表示釋放一個寫者,通知等待的寫者。
4、runtime_Semrelease(&rw.readerSem, false, 0):
這個方法用于釋放讀鎖。
&rw.readerSem 是讀鎖等待隊列的信號量。
false 表示不以 LIFO 模式進行等待隊列的管理。
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解決分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-06-06Golang語言如何讀取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