Go - sync.RWMutex

設(shè)計(jì)目的

大多數(shù)讀請(qǐng)求之間互不影響,在讀多寫少的場(chǎng)景下,可以分離讀寫操作,提高讀寫并發(fā)性能.

限制

只能讀讀并發(fā), 讀寫, 寫寫操作不并發(fā)

RWMutex

RWMutex 在某一時(shí)刻只能由任意數(shù)量的 reader 持有,或者是只被單個(gè)的 writer 持有。

RWMutex 的方法總共有 5 個(gè)。

  1. Lock/Unlock:寫操作時(shí)調(diào)用的方法。如果鎖已經(jīng)被 reader 或者 writer 持有,那么,Lock 方法會(huì)一直阻塞,直到能獲取到鎖;Unlock 則是配對(duì)的釋放鎖的方法。
  2. RLock/RUnlock:讀操作時(shí)調(diào)用的方法。如果鎖已經(jīng)被 writer 持有的話,RLock 方法會(huì)一直阻塞,直到能獲取到鎖,否則就直接返回;而 RUnlock 是 reader 釋放鎖的方法。
  3. RLocker:這個(gè)方法的作用是為讀操作返回一個(gè) Locker 接口的對(duì)象。它的 Lock 方法會(huì)調(diào)用 RWMutex 的 RLock 方法,它的 Unlock 方法會(huì)調(diào)用 RWMutex 的 RUnlock 方法。

RWMutex 的零值是未加鎖的狀態(tài),所以,當(dāng)你使用 RWMutex 的時(shí)候,無論是聲明變量,還是嵌入到其它 struct 中,都不必顯式地初始化。

使用場(chǎng)景

如果遇到可以明確區(qū)分 reader 和 writer goroutine 的場(chǎng)景,且有大量的并發(fā)讀、少量的并發(fā)寫,并且有強(qiáng)烈的性能需求,你就可以考慮使用讀寫鎖 RWMutex 替換 Mutex。

實(shí)現(xiàn)原理

RWMutex 是基于 Mutex 實(shí)現(xiàn)的

readers-writers 問題一般有三類,基于對(duì)讀和寫操作的優(yōu)先級(jí),讀寫鎖的設(shè)計(jì)和實(shí)現(xiàn)也分成三類。

  • Read-preferring:讀優(yōu)先的設(shè)計(jì)可以提供很高的并發(fā)性,但是,在競(jìng)爭激烈的情況下可能會(huì)導(dǎo)致寫?zhàn)囸I。這是因?yàn)?,如果有大量的讀,這種設(shè)計(jì)會(huì)導(dǎo)致只有所有的讀都釋放了鎖之后,寫才可能獲取到鎖。

  • Write-preferring:寫優(yōu)先的設(shè)計(jì)意味著,如果已經(jīng)有一個(gè) writer 在等待請(qǐng)求鎖的話,它會(huì)阻止新來的請(qǐng)求鎖的 reader 獲取到鎖,所以優(yōu)先保障 writer。當(dāng)然,如果有一些 reader 已經(jīng)請(qǐng)求了鎖的話,新請(qǐng)求的 writer 也會(huì)等待已經(jīng)存在的 reader 都釋放鎖之后才能獲取。所以,寫優(yōu)先級(jí)設(shè)計(jì)中的優(yōu)先權(quán)是針對(duì)新來的請(qǐng)求而言的。這種設(shè)計(jì)主要避免了 writer 的饑餓問題。

  • 不指定優(yōu)先級(jí):這種設(shè)計(jì)比較簡單,不區(qū)分 reader 和 writer 優(yōu)先級(jí),某些場(chǎng)景下這種不指定優(yōu)先級(jí)的設(shè)計(jì)反而更有效,因?yàn)榈谝活悆?yōu)先級(jí)會(huì)導(dǎo)致寫?zhàn)囸I,第二類優(yōu)先級(jí)可能會(huì)導(dǎo)致讀饑餓,這種不指定優(yōu)先級(jí)的訪問不再區(qū)分讀寫,大家都是同一個(gè)優(yōu)先級(jí),解決了饑餓的問題。

Go 標(biāo)準(zhǔn)庫中的 RWMutex 設(shè)計(jì)是 Write-preferring 方案。一個(gè)正在阻塞的 Lock 調(diào)用會(huì)排除新的 reader 請(qǐng)求到鎖。

RWMutex 包含一個(gè) Mutex,以及四個(gè)輔助字段 writerSem、readerSem、readerCount 和 readerWait:

type RWMutex struct {
  w           Mutex   // 互斥鎖解決多個(gè)writer的競(jìng)爭
  writerSem   uint32  // writer信號(hào)量
  readerSem   uint32  // reader信號(hào)量
  readerCount int32   // reader的數(shù)量
  readerWait  int32   // writer等待完成的reader的數(shù)量
}

const rwmutexMaxReaders = 1 << 30
  • 字段 w:為 writer 的競(jìng)爭鎖而設(shè)計(jì);
  • 字段 readerCount:記錄當(dāng)前 reader 的數(shù)量(以及是否有 writer 競(jìng)爭鎖);
  • readerWait:記錄 writer 請(qǐng)求鎖時(shí)需要等待 read 完成的 reader 的數(shù)量;
  • writerSem 和 readerSem:都是為了阻塞設(shè)計(jì)的信號(hào)量。
  • 這里的常量 rwmutexMaxReaders,定義了最大的 reader 數(shù)量。

寫鎖加鎖過程

  1. 嘗試獲取寫鎖,如果鎖被占用,則本goroutine會(huì)進(jìn)入自旋或者休眠.
  2. 判斷當(dāng)前執(zhí)行讀操作協(xié)程數(shù)量,如果不為0,先設(shè)置要等待的讀操作數(shù)量,然后設(shè)置等待寫等待讀信號(hào)量進(jìn)入休眠狀態(tài),等待所有讀鎖執(zhí)行結(jié)束后釋放信號(hào)量將當(dāng)前協(xié)程喚醒.

寫鎖解鎖過程

  1. 通過設(shè)置readCount成正數(shù),釋放讀鎖.
  2. for循環(huán)釋放所有因?yàn)楂@取讀鎖而陷入等待的Groutine.
  3. 釋放寫鎖.

讀鎖加鎖過程

  1. 直接對(duì)readCount進(jìn)行+1原子操作,>0則代表沒有g(shù)oroutine獲取寫鎖, 讀鎖獲取成功.
  2. 如果<0則代表有g(shù)oroutine獲取寫鎖,等待讀等待寫信號(hào)量進(jìn)入休眠狀態(tài),等待寫鎖執(zhí)行結(jié)束后釋放信號(hào)量.

讀鎖解鎖過程

  1. 直接對(duì)readCount進(jìn)行-1原子操作,如果>=0代表釋放成功.
  2. 如果<0,代表有寫操作在等待讀鎖釋放,將readerWait數(shù)量-1,如果結(jié)果==0,則觸發(fā)寫等待讀信號(hào)量喚醒嘗試獲取寫鎖的goroutine.

加鎖解鎖總結(jié)

獲取寫鎖時(shí)會(huì)先阻塞寫鎖的獲取,后阻塞讀鎖的獲取,解鎖時(shí)先釋放讀鎖,喚醒等待的讀操作,再釋放寫鎖,這種策略能夠保證讀操作不會(huì)被連續(xù)的寫操作『餓死』。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容