一文掌握go的sync.RWMutex鎖
在簡(jiǎn)略的說(shuō)之前,首先要對(duì)RW鎖的結(jié)構(gòu)有一個(gè)大致的了解
type RWMutex struct { w Mutex // 寫鎖互斥鎖,只鎖寫鎖,和讀鎖無(wú)關(guān) writerSem uint32 // sema鎖--用于“寫協(xié)程”排隊(duì)等待 readerSem uint32 // sema鎖--用于“讀協(xié)程”排隊(duì)等待 readerCount int32 // 讀鎖的計(jì)數(shù)器 readerWait int32 // 等待讀鎖釋放的數(shù)量 }
這里要額外說(shuō)一句,writerSem和readerSem底層都是semaRoot,這個(gè)結(jié)構(gòu)體有興趣可以了解下,他的用法有點(diǎn)類似于一個(gè)簡(jiǎn)版的channel,很多地方把他的初始值設(shè)置為0,使得所有想獲取該sema鎖的協(xié)程都排隊(duì)等待,也就是說(shuō)初始值為0的sema鎖,他本身起到的作用是成為一個(gè)協(xié)程等待隊(duì)列,就像沒(méi)有緩沖區(qū)的channel一樣。
好現(xiàn)在進(jìn)入正題。本文是為了在面試中能快速口述RW鎖,并非為了完整解答RW鎖的機(jī)制。
前提:
readerCount這個(gè)參數(shù)非常重要
- 為負(fù)數(shù)時(shí):說(shuō)明此鎖已經(jīng)被寫協(xié)程占據(jù),所有渴望加讀鎖的協(xié)程被阻塞在readerSem
- 為正數(shù)時(shí):正數(shù)的數(shù)值為當(dāng)前持有該鎖的所有讀協(xié)程的數(shù)量總和,所有渴望加寫鎖的協(xié)程被阻塞在writerSem
讀寫鎖互斥性
- 讀鎖是并發(fā)的,可以多個(gè)協(xié)程持有一把讀鎖。
- 寫鎖是唯一的,互斥的,同一時(shí)刻只能有一個(gè)寫協(xié)程擁有寫鎖
- 讀鎖和寫鎖是互斥的,寫鎖生效時(shí),是不能有讀鎖被獲取到,同樣,必須所有的讀鎖都被釋放,或者壓根沒(méi)有讀協(xié)程獲取讀鎖,寫鎖方可被獲取。
一個(gè)很重要的參數(shù):const rwmutexMaxReaders = 1 << 30 ,rwmutexMaxReaders 非常大,意思是最多能有rwmutexMaxReaders(1 << 30 十進(jìn)制為 4294967296)個(gè)協(xié)程同時(shí)持有讀鎖。
寫鎖上鎖場(chǎng)景:
首先分析寫鎖,因?yàn)樽x鎖的很多操作是根據(jù)寫鎖來(lái)的,如果一上來(lái)就說(shuō)讀鎖,很多東西沒(méi)法串起來(lái)
func (rw *RWMutex) Lock() { // race.Enabled是官方的一些測(cè)試,性能檢測(cè)的東西,無(wú)需關(guān)心,這個(gè)只在編譯階段才能啟用 if race.Enabled { _ = rw.w.state race.Disable() } // First, resolve competition with other writers. rw.w.Lock() // Announce to readers there is a pending writer. r := atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders) + rwmutexMaxReaders // Wait for active readers. if r != 0 && atomic.AddInt32(&rw.readerWait, r) != 0 { runtime_SemacquireMutex(&rw.writerSem, false, 0) } if race.Enabled { race.Enable() race.Acquire(unsafe.Pointer(&rw.readerSem)) race.Acquire(unsafe.Pointer(&rw.writerSem)) } }
1.獲取寫鎖--沒(méi)有讀鎖等待
- rw.w.Lock進(jìn)行加鎖,阻塞后續(xù)的其他寫協(xié)程的鎖請(qǐng)求。
- atomic.AddInt32進(jìn)行原子操作,減去rwmutexMaxReaders,減成功才說(shuō)明沒(méi)有并發(fā)問(wèn)題,可以繼續(xù)下面的操作。然后再加上rwmutexMaxReaders,得到真正的readerCount的數(shù)值。
- 此時(shí)還需要再次進(jìn)行一個(gè)原子操作,把當(dāng)前readerCount的值搬運(yùn)到readerWait里面,意思是當(dāng)前要獲取寫鎖的協(xié)程需要等待的讀鎖的數(shù)量。
- 此時(shí)readerCount只有兩種情況,一種是0,一種是正數(shù),因?yàn)橹挥袑戞i上的時(shí)候才為負(fù)數(shù),而上面的操作已經(jīng)還原了加寫鎖之前的值,而w.Lock保證了不會(huì)有2個(gè)及以上的寫協(xié)程去同時(shí)操作
- readerCount 如果是 0,加鎖成功。
- 如果不為0則說(shuō)明有讀鎖等待,詳見場(chǎng)景2
2.獲取寫鎖--有讀鎖等待
- 接上面的判斷,如果readrCount不為0,說(shuō)明前面有讀鎖正在運(yùn)行,寫鎖需要等待所有讀鎖釋放才能獲取寫鎖,當(dāng)前協(xié)程執(zhí)行 runtime_SemacquireMutex 進(jìn)入 waiterSem 的休眠隊(duì)列等待被喚醒
3.獲取寫鎖--前面已經(jīng)有寫鎖了
后面的寫協(xié)程也調(diào)用 rw.w.Lock() 進(jìn)行加鎖,因?yàn)榍懊嬗袑戞i已經(jīng)獲取了w,所以后續(xù)的寫協(xié)程會(huì)因?yàn)楂@取不到w,而進(jìn)入到w的sema隊(duì)列里面,w是一個(gè)mutex的鎖,mutex鎖里是一個(gè)sema鎖,sema鎖因?yàn)闆](méi)有設(shè)置初始值,所以退化為一個(gè)隊(duì)列,而獲取不到w鎖的就會(huì)直接被阻塞在w的sema隊(duì)列里,從而無(wú)法進(jìn)行接下來(lái)的操作
寫鎖釋放鎖場(chǎng)景:
func (rw *RWMutex) Unlock() { if race.Enabled { _ = rw.w.state race.Release(unsafe.Pointer(&rw.readerSem)) race.Disable() } // Announce to readers there is no active writer. r := atomic.AddInt32(&rw.readerCount, rwmutexMaxReaders) if r >= rwmutexMaxReaders { race.Enable() throw("sync: Unlock of unlocked RWMutex") } // Unblock blocked readers, if any. for i := 0; i < int(r); i++ { runtime_Semrelease(&rw.readerSem, false, 0) } // Allow other writers to proceed. rw.w.Unlock() if race.Enabled { race.Enable() } }
1.釋放寫鎖--后面【沒(méi)有】讀鎖等待
- 執(zhí)行atomic.AddInt32進(jìn)行原子操作,把已經(jīng)為負(fù)值的readerCount還原為正數(shù),此時(shí)已經(jīng)算釋放了寫鎖
- (此步驟不重要,就是個(gè)判錯(cuò))如果還原后的readerCount比rwmutexMaxReaders還大,這就是說(shuō)明出錯(cuò)了,直接throw彈出錯(cuò)誤,throw這個(gè)方法是內(nèi)部方法,對(duì)go來(lái)說(shuō)就是panic了
- 此場(chǎng)景因?yàn)闆](méi)有讀鎖等待,此時(shí)的readerCount為0,不會(huì)進(jìn)入for循環(huán),直接rw.w.Unlock釋放w鎖,允許其他寫協(xié)程加鎖,此時(shí)其他的寫協(xié)程會(huì)被從w里的sema隊(duì)列里喚醒
2.釋放寫鎖--后面【有】讀鎖等待
- 接場(chǎng)景1,原子操作readerCount釋放寫鎖后,如果r是大于0,說(shuō)明有讀鎖等待,for循環(huán)readerSem里面所有的等待的讀協(xié)程,因?yàn)樽x鎖是共享鎖,所以所有的讀協(xié)程都會(huì)獲取鎖并被喚醒
- rw.w.Unlock釋放w鎖,允許其他寫協(xié)程加鎖,其他的寫協(xié)程會(huì)被從w里的sema隊(duì)列里喚醒
3.釋放寫鎖--后面有【寫鎖】等待
- 上接場(chǎng)景2,當(dāng)rw.w.Unlock釋放w鎖,其他的寫協(xié)程會(huì)被從w里的sema隊(duì)列里喚醒
- 寫鎖釋放的時(shí)候,是先喚醒所有等待的讀鎖,再解除rw.w鎖,所以,并不會(huì)造成讀鎖的饑餓
- 后面讀鎖再次對(duì)rw.w進(jìn)行上鎖,重復(fù)上面所述寫鎖獲取鎖的場(chǎng)景
讀鎖上鎖場(chǎng)景:
func (rw *RWMutex) RLock() { // race.Enabled都是測(cè)試用的代碼,在閱讀源碼的時(shí)候可以跳過(guò) if race.Enabled { _ = rw.w.state race.Disable() } if atomic.AddInt32(&rw.readerCount, 1) < 0 { // A writer is pending, wait for it. runtime_SemacquireMutex(&rw.readerSem, false, 0) } if race.Enabled { race.Enable() race.Acquire(unsafe.Pointer(&rw.readerSem)) } }
1.獲取讀鎖--此時(shí)沒(méi)有寫鎖.
最簡(jiǎn)單的場(chǎng)景,協(xié)程對(duì)rw.readerCount進(jìn)行原子操作加一,如果得到的結(jié)果為正數(shù),說(shuō)明獲取讀鎖成功。
2.獲取讀鎖--前方已經(jīng)有寫鎖搶占了該鎖
- 當(dāng)協(xié)程對(duì)rw.readerCount進(jìn)行原子加1操作的時(shí)候,發(fā)現(xiàn)加完,readerCount還是負(fù)數(shù),說(shuō)明在這個(gè)時(shí)間點(diǎn)以前,已經(jīng)有協(xié)程獲取了寫鎖
- runtime_SemacquireMutex 方法將當(dāng)前協(xié)程加入readerSem隊(duì)列,等待寫鎖釋放后被批量喚醒(寫鎖釋放會(huì)一次性放出所有的堆積的讀協(xié)程)
3.獲取讀鎖--前方有寫鎖搶已經(jīng)被搶占,后方有寫鎖等待
- 寫鎖在獲取的時(shí)候,對(duì)RWMutex.w進(jìn)行加鎖,是獨(dú)占鎖,如果前方一個(gè)寫鎖已經(jīng)得到了鎖正在處理業(yè)務(wù),那么后方的寫鎖進(jìn)來(lái)就會(huì)發(fā)現(xiàn)加不上鎖,直接在rw.w.lock階段就阻塞了,后面的邏輯是無(wú)法繼續(xù)運(yùn)行的,所以進(jìn)入不了writerSem,它只會(huì)進(jìn)入到w這個(gè)mutex鎖的sema隊(duì)列里,讀鎖則進(jìn)入休眠隊(duì)列readerSem
讀鎖釋放鎖場(chǎng)景:
func (rw *RWMutex) RUnlock() { if race.Enabled { _ = rw.w.state race.ReleaseMerge(unsafe.Pointer(&rw.writerSem)) race.Disable() } if r := atomic.AddInt32(&rw.readerCount, -1); r < 0 { // Outlined slow-path to allow the fast-path to be inlined rw.rUnlockSlow(r) } if race.Enabled { race.Enable() } }
1.釋放讀鎖--后方?jīng)]有寫鎖等待
- atomic.AddInt32 進(jìn)行原子操作,讓readerCount 減1,操作后,如果readerCount 大于0,說(shuō)明后方是沒(méi)有寫鎖等待的,釋放鎖后整個(gè)流程就結(jié)束了
2.釋放讀鎖--后方有寫鎖等待
- 原子操作eaderCount 減1后,發(fā)現(xiàn)eaderCount是小于0的,此時(shí)說(shuō)明已經(jīng)有等待寫鎖的協(xié)程在嘗試獲取寫鎖。執(zhí)行 rw.rUnlockSlow(r) 。
func (rw *RWMutex) rUnlockSlow(r int32) { if r+1 == 0 || r+1 == -rwmutexMaxReaders { race.Enable() throw("sync: RUnlock of unlocked RWMutex") } // A writer is pending. if atomic.AddInt32(&rw.readerWait, -1) == 0 { // The last reader unblocks the writer. runtime_Semrelease(&rw.writerSem, false, 1) } }
這里是有個(gè)前提的,上面提到(詳見上面的獲取寫鎖的場(chǎng)景1),如果寫協(xié)程進(jìn)來(lái)想加寫鎖,需要把它需要等待的讀鎖數(shù)量從readerCount里賦值給readerWait。當(dāng)它等待的讀鎖釋放后,就需要用rUnlockSlow方法對(duì)readerWait進(jìn)行減1,如果readWait == 0 ,說(shuō)明這是最后一個(gè)需要等待的讀鎖也釋放了,釋放后就通知該寫鎖可以被喚醒了,鎖給你了。
到此這篇關(guān)于go的sync.RWMutex鎖的文章就介紹到這了,更多相關(guān)go的sync.RWMutex鎖內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Golang 語(yǔ)言極簡(jiǎn)類型轉(zhuǎn)換庫(kù)cast的使用詳解
本文我們通過(guò) cast.ToString() 函數(shù)的使用,簡(jiǎn)單介紹了cast 的使用方法,除此之外,它還支持很多其他類型,在這沒(méi)有多多介紹,對(duì)Golang 類型轉(zhuǎn)換庫(kù) cast相關(guān)知識(shí)感興趣的朋友一起看看吧2021-11-11Golang實(shí)現(xiàn)文件夾的創(chuàng)建與刪除的方法詳解
這篇文章主要介紹了如何利用Go語(yǔ)言實(shí)現(xiàn)對(duì)文件夾的常用操作:創(chuàng)建于刪除。文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2022-05-05Go語(yǔ)言Http調(diào)用之Post請(qǐng)求詳解
前文我們介紹了如何進(jìn)行 HTTP 調(diào)用,并通過(guò) GET 請(qǐng)求的例子,講述了 query 參數(shù)和 header 參數(shù)如何設(shè)置,以及響應(yīng)體的獲取方法。 本文繼上文,接下來(lái)會(huì)通過(guò) POST 請(qǐng)求,對(duì)其他參數(shù)的設(shè)置進(jìn)行介紹,感興趣的可以了解一下2022-12-12Golang實(shí)現(xiàn)DFA算法對(duì)敏感詞過(guò)濾功能
DFA算法是確定性有限自動(dòng)機(jī),其特征是,有一個(gè)有限狀態(tài)集合和一些從一個(gè)狀態(tài)通向另一個(gè)狀態(tài)的邊,每條邊上標(biāo)記有一個(gè)符號(hào),通俗的講DFA算法就是把你要匹配的做成一顆字典樹,然后對(duì)你輸入的內(nèi)容進(jìn)行匹配的過(guò)程,本文將利用DFA算法實(shí)現(xiàn)敏感詞過(guò)濾,需要的可以參考一下2023-10-10

GoLang中sql.Exec()報(bào)錯(cuò)解決辦法

Go 自定義package包設(shè)置與導(dǎo)入操作