golang RWMutex讀寫(xiě)鎖實(shí)現(xiàn)讀共享寫(xiě)?yīng)氄嫉墓δ苁纠?/h1>
更新時(shí)間:2023年09月27日 09:22:57 作者:lincoln_hlf1
在 Go 里除了互斥鎖外,還有讀寫(xiě)鎖 RWMutex,它主要用來(lái)實(shí)現(xiàn)讀共享,寫(xiě)?yīng)氄嫉墓δ?今天我們也順便分析下讀寫(xiě)鎖,加深對(duì) Go 鎖的理解
引言
在上一篇文章 golang 重要知識(shí):mutex 里我們介紹了互斥鎖 mutex 的相關(guān)原理實(shí)現(xiàn)。而且在 Go 里除了互斥鎖外,還有讀寫(xiě)鎖 RWMutex,它主要用來(lái)實(shí)現(xiàn)讀共享,寫(xiě)?yīng)氄嫉墓δ堋=裉煳覀円岔槺惴治鱿伦x寫(xiě)鎖,加深對(duì) Go 鎖的理解
讀寫(xiě)鎖的實(shí)現(xiàn)原理
所謂的讀寫(xiě)鎖,其實(shí)就是針對(duì)下面的兩種場(chǎng)景,對(duì) Goroutine 之間的同步互斥進(jìn)行控制:
- 多個(gè) goroutine 一起占有讀鎖,互不影響,可以繼續(xù)自己后面的邏輯代碼。
- 寫(xiě)鎖正在占有著,則后面的 goroutine 無(wú)論是要進(jìn)行讀鎖占有,還是寫(xiě)鎖占有,都將會(huì)被阻塞等待,直到當(dāng)前的寫(xiě)鎖釋放。
弄清楚上面的場(chǎng)景需求后,實(shí)現(xiàn)就簡(jiǎn)單多了,關(guān)鍵就在于判斷當(dāng)前是否處于寫(xiě)鎖狀態(tài)即可,畢竟需要有阻塞等待的動(dòng)作。
按照常規(guī)思路,我們一般會(huì)采用一個(gè)標(biāo)識(shí)位來(lái)維護(hù)這個(gè)狀態(tài)。然而,Go 官方卻連這一步都省了。
利用了一個(gè)本來(lái)就得維護(hù)的讀鎖數(shù)量,在進(jìn)行寫(xiě)鎖占有時(shí),使它變?yōu)樨?fù)數(shù)。
后面有新進(jìn)來(lái)的讀寫(xiě)操作,只需要判斷該值是否正負(fù)即可,負(fù)數(shù)則代表當(dāng)前正在進(jìn)行寫(xiě)鎖占有,需要阻塞等待。
而在寫(xiě)鎖占有結(jié)束后,該值又會(huì)恢復(fù)為正數(shù),又可以進(jìn)行新的讀寫(xiě)操作了。
RWMutex 源碼分析
接下來(lái),我們到 src/runtime/rwmutex.go里具體分析下 RWMutex 的代碼結(jié)構(gòu)。
// rwmutex 是一個(gè)讀寫(xiě)互斥的鎖
// 將允許多個(gè) goroutine 持有讀鎖,但寫(xiě)鎖只會(huì)有一個(gè)持有
// rwmutex 使用了 sync.RWMutex 來(lái)輔助寫(xiě)鎖互斥
type rwmutex struct {
rLock mutex // 用于保護(hù)設(shè)置 readers, readerPass, writer
readers muintptr // 休眠等待的 goroutine 讀鎖隊(duì)列,等到寫(xiě)鎖占有結(jié)束后將對(duì)應(yīng)被喚起。
readerPass uint32 // 讀鎖隊(duì)列需要跳過(guò)的 goroutine 數(shù)量,當(dāng)在寫(xiě)鎖結(jié)束后會(huì)喚起讀鎖隊(duì)列里的 goroutine,但有的可能已不在隊(duì)列里了,這部分需跳過(guò)。
wLock mutex // 用于 writer 之間的互斥鎖
writer muintptr // 等待讀完成的 writer
readerCount uint32 // 正在執(zhí)行讀操作的 goroutine數(shù)量
readerWait uint32 // 等待讀鎖釋放的數(shù)量。當(dāng)寫(xiě)鎖占有后,前面還有部分讀鎖在繼續(xù)著,需要等它們釋放才能繼續(xù)進(jìn)行。
}
RWMutex 的 Lock() 分析
func (rw *rwmutex) Lock() {
// 用于多個(gè)寫(xiě)鎖之間的的競(jìng)爭(zhēng)
lock(&rw.wLock)
m := getg().m
// 將讀鎖數(shù)量 readerCount 置為負(fù)數(shù),用于判斷當(dāng)前是否處于寫(xiě)鎖占有狀態(tài),
// rw.readerCount < 0 則表示當(dāng)前正在進(jìn)行寫(xiě)鎖占有.
r := int32(atomic.Xadd(&rw.readerCount, -rwmutexMaxReaders)) + rwmutexMaxReaders
// 前面還有讀鎖在進(jìn)行著,需要等待釋放完才能繼續(xù)
lock(&rw.rLock)
if r != 0 && atomic.Xadd(&rw.readerWait, r) != 0 {
systemstack(func() {
rw.writer.set(m)
unlock(&rw.rLock)
notesleep(&m.park)
noteclear(&m.park)
})
} else {
unlock(&rw.rLock)
}
}
RWMutex 的 RLock() 分析
func (rw *rwmutex) Rlock() {
acquirem()
if int32(atomic.Xadd(&rw.readerCount, 1)) < 0 {
// 讀鎖數(shù)量 readerCount + 1 后小于 0,表示當(dāng)前正被寫(xiě)鎖占有,
// 等待寫(xiě)鎖釋放
systemstack(func() {
lock(&rw.rLock)
if rw.readerPass > 0 {
rw.readerPass -= 1
unlock(&rw.rLock)
} else {
// 等待寫(xiě)鎖喚起
m := getg().m
m.schedlink = rw.readers
rw.readers.set(m)
unlock(&rw.rLock)
notesleep(&m.park)
noteclear(&m.park)
}
})
}
}
RWMutex 的 Unlock() 分析
func (rw *rwmutex) Unlock() {
// 將原來(lái)被寫(xiě)鎖置為負(fù)數(shù)的 readerCount 重新恢復(fù)回來(lái).
r := int32(atomic.Xadd(&rw.readerCount, rwmutexMaxReaders))
if r >= rwmutexMaxReaders {
throw("unlock of unlocked rwmutex")
}
// 喚起之前等待的讀鎖.
lock(&rw.rLock)
for rw.readers.ptr() != nil {
reader := rw.readers.ptr()
rw.readers = reader.schedlink
reader.schedlink.set(nil)
notewakeup(&reader.park)
r -= 1
}
// 如果 r > 0, 說(shuō)明讀鎖隊(duì)列里有的 goroutine 已不在隊(duì)列里了,這部分需跳過(guò)
rw.readerPass += uint32(r)
unlock(&rw.rLock)
// 解除寫(xiě)鎖
unlock(&rw.wLock)
}
RWMutex 的 RUnlock() 分析
func (rw *rwmutex) RUnlock() {
// 如果釋放后,readerCount < 0,表示當(dāng)前寫(xiě)鎖正在占有
if r := int32(atomic.Xadd(&rw.readerCount, -1)); r < 0 {
if r+1 == 0 || r+1 == -rwmutexMaxReaders {
throw("runlock of unlocked rwmutex")
}
// readerWait == 0,表示前面的讀鎖都釋放完了,
// 需要喚起寫(xiě)鎖
if atomic.Xadd(&rw.readerWait, -1) == 0 {
// The last reader unblocks the writer.
lock(&rw.rLock)
w := rw.writer.ptr()
if w != nil {
notewakeup(&w.park)
}
unlock(&rw.rLock)
}
}
releasem(getg().m)
}
總結(jié)
RWMutex 通過(guò) readerCount 的正負(fù)來(lái)判斷當(dāng)前是處于讀鎖占有還是寫(xiě)鎖占有。
在處于寫(xiě)鎖占有狀態(tài)后,會(huì)將此時(shí)的 readerCount 賦值給 readerWait,表示要等前面 readerWait 個(gè)讀鎖釋放完才算完整的占有寫(xiě)鎖,才能進(jìn)行后面的獨(dú)占操作。
讀鎖釋放的時(shí)候, 會(huì)對(duì) readerWait 對(duì)應(yīng)減一,直到為 0 值,就可以喚起寫(xiě)鎖了。
并且在寫(xiě)鎖占有后,即時(shí)有新的讀操作加進(jìn)來(lái), 也不會(huì)影響到 readerWait 值了,只會(huì)影響總的讀鎖數(shù)目:readerCount。
以上就是golang RWMutex讀寫(xiě)鎖實(shí)現(xiàn)讀共享寫(xiě)?yīng)氄嫉墓δ苁纠脑敿?xì)內(nèi)容,更多關(guān)于golang RWMutex讀寫(xiě)鎖的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
您可能感興趣的文章:
相關(guān)文章
-
Go語(yǔ)言連接Oracle數(shù)據(jù)庫(kù)的方法
這篇文章主要介紹了Go語(yǔ)言連接Oracle數(shù)據(jù)庫(kù)的方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下 2021-02-02
-
golang 后臺(tái)進(jìn)程的啟動(dòng)和停止操作
這篇文章主要介紹了golang 后臺(tái)進(jìn)程的啟動(dòng)和停止操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧 2021-04-04
-
手把手教你vscode配置golang開(kāi)發(fā)環(huán)境的步驟
這篇文章主要介紹了手把手教你vscode配置golang開(kāi)發(fā)環(huán)境的步驟,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下 2021-03-03
-
深入解析golang中的標(biāo)準(zhǔn)庫(kù)flag
Go語(yǔ)言內(nèi)置的flag包實(shí)現(xiàn)了命令行參數(shù)的解析,flag包使得開(kāi)發(fā)命令行工具更為簡(jiǎn)單,下面通過(guò)本文給大家詳細(xì)介紹下golang中的標(biāo)準(zhǔn)庫(kù)flag相關(guān)知識(shí),感興趣的朋友一起看看吧 2021-11-11
-
Go語(yǔ)言使用net/http實(shí)現(xiàn)簡(jiǎn)單登錄驗(yàn)證和文件上傳功能
這篇文章主要介紹了Go語(yǔ)言使用net/http實(shí)現(xiàn)簡(jiǎn)單登錄驗(yàn)證和文件上傳功能,使用net/http模塊編寫(xiě)了一個(gè)簡(jiǎn)單的登錄驗(yàn)證和文件上傳的功能,在此做個(gè)簡(jiǎn)單記錄,需要的朋友可以參考下 2023-07-07
-
Go中字符串處理?fmt.Sprintf與string.Builder的區(qū)別對(duì)比分析
在Go語(yǔ)言中,我們通常會(huì)遇到兩種主要的方式來(lái)處理和操作字符串:使用fmt.Sprintf函數(shù)和string.Builder類(lèi)型,本文給大家介紹它們?cè)谛阅芎陀梅ㄉ嫌幸恍╆P(guān)鍵區(qū)別,感興趣的朋友跟隨小編一起看看吧 2023-11-11
最新評(píng)論
引言
在上一篇文章 golang 重要知識(shí):mutex 里我們介紹了互斥鎖 mutex 的相關(guān)原理實(shí)現(xiàn)。而且在 Go 里除了互斥鎖外,還有讀寫(xiě)鎖 RWMutex,它主要用來(lái)實(shí)現(xiàn)讀共享,寫(xiě)?yīng)氄嫉墓δ堋=裉煳覀円岔槺惴治鱿伦x寫(xiě)鎖,加深對(duì) Go 鎖的理解
讀寫(xiě)鎖的實(shí)現(xiàn)原理
所謂的讀寫(xiě)鎖,其實(shí)就是針對(duì)下面的兩種場(chǎng)景,對(duì) Goroutine 之間的同步互斥進(jìn)行控制:
- 多個(gè) goroutine 一起占有讀鎖,互不影響,可以繼續(xù)自己后面的邏輯代碼。
- 寫(xiě)鎖正在占有著,則后面的 goroutine 無(wú)論是要進(jìn)行讀鎖占有,還是寫(xiě)鎖占有,都將會(huì)被阻塞等待,直到當(dāng)前的寫(xiě)鎖釋放。
弄清楚上面的場(chǎng)景需求后,實(shí)現(xiàn)就簡(jiǎn)單多了,關(guān)鍵就在于判斷當(dāng)前是否處于寫(xiě)鎖狀態(tài)即可,畢竟需要有阻塞等待的動(dòng)作。
按照常規(guī)思路,我們一般會(huì)采用一個(gè)標(biāo)識(shí)位來(lái)維護(hù)這個(gè)狀態(tài)。然而,Go 官方卻連這一步都省了。
利用了一個(gè)本來(lái)就得維護(hù)的讀鎖數(shù)量,在進(jìn)行寫(xiě)鎖占有時(shí),使它變?yōu)樨?fù)數(shù)。
后面有新進(jìn)來(lái)的讀寫(xiě)操作,只需要判斷該值是否正負(fù)即可,負(fù)數(shù)則代表當(dāng)前正在進(jìn)行寫(xiě)鎖占有,需要阻塞等待。
而在寫(xiě)鎖占有結(jié)束后,該值又會(huì)恢復(fù)為正數(shù),又可以進(jìn)行新的讀寫(xiě)操作了。
RWMutex 源碼分析
接下來(lái),我們到 src/runtime/rwmutex.go里具體分析下 RWMutex 的代碼結(jié)構(gòu)。
// rwmutex 是一個(gè)讀寫(xiě)互斥的鎖
// 將允許多個(gè) goroutine 持有讀鎖,但寫(xiě)鎖只會(huì)有一個(gè)持有
// rwmutex 使用了 sync.RWMutex 來(lái)輔助寫(xiě)鎖互斥
type rwmutex struct {
rLock mutex // 用于保護(hù)設(shè)置 readers, readerPass, writer
readers muintptr // 休眠等待的 goroutine 讀鎖隊(duì)列,等到寫(xiě)鎖占有結(jié)束后將對(duì)應(yīng)被喚起。
readerPass uint32 // 讀鎖隊(duì)列需要跳過(guò)的 goroutine 數(shù)量,當(dāng)在寫(xiě)鎖結(jié)束后會(huì)喚起讀鎖隊(duì)列里的 goroutine,但有的可能已不在隊(duì)列里了,這部分需跳過(guò)。
wLock mutex // 用于 writer 之間的互斥鎖
writer muintptr // 等待讀完成的 writer
readerCount uint32 // 正在執(zhí)行讀操作的 goroutine數(shù)量
readerWait uint32 // 等待讀鎖釋放的數(shù)量。當(dāng)寫(xiě)鎖占有后,前面還有部分讀鎖在繼續(xù)著,需要等它們釋放才能繼續(xù)進(jìn)行。
}RWMutex 的 Lock() 分析
func (rw *rwmutex) Lock() {
// 用于多個(gè)寫(xiě)鎖之間的的競(jìng)爭(zhēng)
lock(&rw.wLock)
m := getg().m
// 將讀鎖數(shù)量 readerCount 置為負(fù)數(shù),用于判斷當(dāng)前是否處于寫(xiě)鎖占有狀態(tài),
// rw.readerCount < 0 則表示當(dāng)前正在進(jìn)行寫(xiě)鎖占有.
r := int32(atomic.Xadd(&rw.readerCount, -rwmutexMaxReaders)) + rwmutexMaxReaders
// 前面還有讀鎖在進(jìn)行著,需要等待釋放完才能繼續(xù)
lock(&rw.rLock)
if r != 0 && atomic.Xadd(&rw.readerWait, r) != 0 {
systemstack(func() {
rw.writer.set(m)
unlock(&rw.rLock)
notesleep(&m.park)
noteclear(&m.park)
})
} else {
unlock(&rw.rLock)
}
}RWMutex 的 RLock() 分析
func (rw *rwmutex) Rlock() {
acquirem()
if int32(atomic.Xadd(&rw.readerCount, 1)) < 0 {
// 讀鎖數(shù)量 readerCount + 1 后小于 0,表示當(dāng)前正被寫(xiě)鎖占有,
// 等待寫(xiě)鎖釋放
systemstack(func() {
lock(&rw.rLock)
if rw.readerPass > 0 {
rw.readerPass -= 1
unlock(&rw.rLock)
} else {
// 等待寫(xiě)鎖喚起
m := getg().m
m.schedlink = rw.readers
rw.readers.set(m)
unlock(&rw.rLock)
notesleep(&m.park)
noteclear(&m.park)
}
})
}
}RWMutex 的 Unlock() 分析
func (rw *rwmutex) Unlock() {
// 將原來(lái)被寫(xiě)鎖置為負(fù)數(shù)的 readerCount 重新恢復(fù)回來(lái).
r := int32(atomic.Xadd(&rw.readerCount, rwmutexMaxReaders))
if r >= rwmutexMaxReaders {
throw("unlock of unlocked rwmutex")
}
// 喚起之前等待的讀鎖.
lock(&rw.rLock)
for rw.readers.ptr() != nil {
reader := rw.readers.ptr()
rw.readers = reader.schedlink
reader.schedlink.set(nil)
notewakeup(&reader.park)
r -= 1
}
// 如果 r > 0, 說(shuō)明讀鎖隊(duì)列里有的 goroutine 已不在隊(duì)列里了,這部分需跳過(guò)
rw.readerPass += uint32(r)
unlock(&rw.rLock)
// 解除寫(xiě)鎖
unlock(&rw.wLock)
}RWMutex 的 RUnlock() 分析
func (rw *rwmutex) RUnlock() {
// 如果釋放后,readerCount < 0,表示當(dāng)前寫(xiě)鎖正在占有
if r := int32(atomic.Xadd(&rw.readerCount, -1)); r < 0 {
if r+1 == 0 || r+1 == -rwmutexMaxReaders {
throw("runlock of unlocked rwmutex")
}
// readerWait == 0,表示前面的讀鎖都釋放完了,
// 需要喚起寫(xiě)鎖
if atomic.Xadd(&rw.readerWait, -1) == 0 {
// The last reader unblocks the writer.
lock(&rw.rLock)
w := rw.writer.ptr()
if w != nil {
notewakeup(&w.park)
}
unlock(&rw.rLock)
}
}
releasem(getg().m)
}總結(jié)
RWMutex 通過(guò) readerCount 的正負(fù)來(lái)判斷當(dāng)前是處于讀鎖占有還是寫(xiě)鎖占有。
在處于寫(xiě)鎖占有狀態(tài)后,會(huì)將此時(shí)的 readerCount 賦值給 readerWait,表示要等前面 readerWait 個(gè)讀鎖釋放完才算完整的占有寫(xiě)鎖,才能進(jìn)行后面的獨(dú)占操作。
讀鎖釋放的時(shí)候, 會(huì)對(duì) readerWait 對(duì)應(yīng)減一,直到為 0 值,就可以喚起寫(xiě)鎖了。
并且在寫(xiě)鎖占有后,即時(shí)有新的讀操作加進(jìn)來(lái), 也不會(huì)影響到 readerWait 值了,只會(huì)影響總的讀鎖數(shù)目:readerCount。
以上就是golang RWMutex讀寫(xiě)鎖實(shí)現(xiàn)讀共享寫(xiě)?yīng)氄嫉墓δ苁纠脑敿?xì)內(nèi)容,更多關(guān)于golang RWMutex讀寫(xiě)鎖的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Go語(yǔ)言連接Oracle數(shù)據(jù)庫(kù)的方法
這篇文章主要介紹了Go語(yǔ)言連接Oracle數(shù)據(jù)庫(kù)的方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-02-02
golang 后臺(tái)進(jìn)程的啟動(dòng)和停止操作
這篇文章主要介紹了golang 后臺(tái)進(jìn)程的啟動(dòng)和停止操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-04-04
手把手教你vscode配置golang開(kāi)發(fā)環(huán)境的步驟
這篇文章主要介紹了手把手教你vscode配置golang開(kāi)發(fā)環(huán)境的步驟,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-03-03
深入解析golang中的標(biāo)準(zhǔn)庫(kù)flag
Go語(yǔ)言內(nèi)置的flag包實(shí)現(xiàn)了命令行參數(shù)的解析,flag包使得開(kāi)發(fā)命令行工具更為簡(jiǎn)單,下面通過(guò)本文給大家詳細(xì)介紹下golang中的標(biāo)準(zhǔn)庫(kù)flag相關(guān)知識(shí),感興趣的朋友一起看看吧2021-11-11
Go語(yǔ)言使用net/http實(shí)現(xiàn)簡(jiǎn)單登錄驗(yàn)證和文件上傳功能
這篇文章主要介紹了Go語(yǔ)言使用net/http實(shí)現(xiàn)簡(jiǎn)單登錄驗(yàn)證和文件上傳功能,使用net/http模塊編寫(xiě)了一個(gè)簡(jiǎn)單的登錄驗(yàn)證和文件上傳的功能,在此做個(gè)簡(jiǎn)單記錄,需要的朋友可以參考下2023-07-07
Go中字符串處理?fmt.Sprintf與string.Builder的區(qū)別對(duì)比分析
在Go語(yǔ)言中,我們通常會(huì)遇到兩種主要的方式來(lái)處理和操作字符串:使用fmt.Sprintf函數(shù)和string.Builder類(lèi)型,本文給大家介紹它們?cè)谛阅芎陀梅ㄉ嫌幸恍╆P(guān)鍵區(qū)別,感興趣的朋友跟隨小編一起看看吧2023-11-11

