Go并發(fā)之RWMutex的源碼解析詳解
RWMutex
是一個(gè)支持并行讀串行寫的讀寫鎖。RWMutex
具有寫操作優(yōu)先的特點(diǎn),寫操作發(fā)生時(shí),僅允許正在執(zhí)行的讀操作執(zhí)行,后續(xù)的讀操作都會(huì)被阻塞。
使用場(chǎng)景
RWMutex
常用于大量并發(fā)讀,少量并發(fā)寫的場(chǎng)景;比如微服務(wù)配置更新、交易路由緩存等場(chǎng)景。相對(duì)于Mutex
互斥鎖,RWMutex
讀寫鎖具有更好的讀性能。
下面以 “多個(gè)協(xié)程并行讀取str變量,一個(gè)協(xié)程每100毫秒定時(shí)更新str變量” 場(chǎng)景為例,進(jìn)行RWMutex
讀寫鎖和Mutex
互斥鎖的性能對(duì)比。
// 基于RWMutex的實(shí)現(xiàn) var rwLock sync.RWMutex var str1 = "hello" func readWithRWLock() string { rwLock.RLock() defer rwLock.RUnlock() return str1 } func writeWithRWLock() { rwLock.Lock() str1 = time.Now().Format("20060102150405") rwLock.Unlock() } // 多個(gè)協(xié)程并行讀取string變量,同時(shí)每100ms對(duì)string變量進(jìn)行1次更新 func BenchmarkRWMutex(b *testing.B) { ticker := time.NewTicker(100 * time.Millisecond) go func() { for range ticker.C { writeWithRWLock() } }() b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { readWithRWLock() } }) } // 基于Mutex實(shí)現(xiàn) var lock sync.Mutex var str2 = "hello" func readWithMutex() string { lock.Lock() defer lock.Unlock() return str2 } func writeWithMutex() { lock.Lock() str2 = time.Now().Format("20060102150405") lock.Unlock() } // 多個(gè)協(xié)程并行讀取string變量,同時(shí)每100ms對(duì)string變量進(jìn)行1次更新 func BenchmarkMutex(b *testing.B) { ticker := time.NewTicker(100 * time.Millisecond) go func() { for range ticker.C { writeWithMutex() } }() b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { readWithMutex() } }) }
RWMutex
讀寫鎖和Mutex
互斥鎖的性能對(duì)比,結(jié)果如下:
# go test 結(jié)果
go test -bench . -benchtime=10s
BenchmarkRWMutex-8 227611413 49.5 ns/op
BenchmarkMutex-8 135363408 87.8 ns/op
PASS
ok demo 37.800s
源碼解析
RWMutex
是一個(gè)寫操作優(yōu)先的讀寫鎖,如下圖所示:
- 寫操作C發(fā)生時(shí),讀操作A和讀操作B正在執(zhí)行,因此寫操作C被掛起;
- 當(dāng)讀操作D發(fā)生時(shí),由于存在寫操作C等待鎖,所以讀操作D被掛起;
- 讀操作A和讀操作B執(zhí)行完成,由于沒有讀操作和寫操作正在執(zhí)行,寫操作C被喚醒執(zhí)行;
- 當(dāng)讀操作E發(fā)生時(shí),由于寫操作C正在執(zhí)行,所以讀操作E被掛起;
- 當(dāng)寫操作C執(zhí)行完成后,讀操作D和讀操作E被喚醒;
RWMutex結(jié)構(gòu)體
RWMutex
由如下變量組成:
rwmutexMaxReaders
:表示RWMutex
能接受的最大讀協(xié)程數(shù)量,超過rwmutexMaxReaders
后會(huì)發(fā)生panic;w
:Mutex
互斥鎖,用于實(shí)現(xiàn)寫操作之間的互斥writerSem
:寫操作操作信號(hào)量;當(dāng)存在讀操作時(shí),寫操作會(huì)被掛起;讀操作全部完成后,通過writerSem
信號(hào)量喚醒寫操作;readerSem
:讀操作信號(hào)量;當(dāng)存在寫操作時(shí),讀操作會(huì)被掛起;寫操作完成后,通過readerSem
信號(hào)量喚醒讀操作;readerCount
:正在執(zhí)行中的讀操作數(shù)量;當(dāng)不存在寫操作時(shí)從0開始計(jì)數(shù),為正數(shù);當(dāng)存在寫操作時(shí)從負(fù)的rwmutexMaxReaders開始計(jì)數(shù),為負(fù)數(shù);readerWait
:寫操作等待讀操作的數(shù)量;當(dāng)執(zhí)行Lock()
方法時(shí),如果當(dāng)前存在讀操作,會(huì)將讀操作的數(shù)量記錄在readerWait
中,并掛起寫操作;讀操作執(zhí)行完成后,會(huì)更新readerWait
,當(dāng)readerWait
為0時(shí),喚醒寫操作;
const rwmutexMaxReaders = 1 << 30 type RWMutex struct { w Mutex // Mutex互斥鎖,用于實(shí)現(xiàn)寫操作之間的互斥 writerSem uint32 // 寫操作信號(hào)量,用于讀操作喚醒寫操作 readerSem uint32 // 讀操作信號(hào)量,用于寫操作喚醒讀操作 readerCount int32 // 讀操作的數(shù)量,不存在寫操作時(shí)從0開始計(jì)數(shù),存在寫操作時(shí)從-rwmutexMaxReaders開始計(jì)數(shù) readerWait int32 // 寫操作等待讀操作的數(shù)量 }
Lock()方法
Lock
方法用于寫操作獲取鎖,其操作如下:
- 獲取
w
互斥鎖,保證同一時(shí)刻只有一個(gè)寫操作執(zhí)行; - 將
readerCount
更新為負(fù)數(shù),使后續(xù)發(fā)生的讀操作被阻塞; - 如果當(dāng)前存在活躍的讀操作
r != 0
,寫操作進(jìn)入阻塞狀態(tài)runtime_SemacquireMutex
;
func (rw *RWMutex) Lock() { // 寫操作之間通過w互斥鎖實(shí)現(xiàn)互斥 rw.w.Lock() // 1.將readerCount更新為負(fù)值,表示當(dāng)前有寫操作;當(dāng)readerCount為負(fù)數(shù)時(shí),新的讀操作會(huì)被掛起 // 2.r表示當(dāng)前正在執(zhí)行的讀操作數(shù)量 r := atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders) + rwmutexMaxReaders // r != 0表示當(dāng)前存在正在執(zhí)行的讀操作;寫操作需要等待所有讀操作執(zhí)行完,才能被執(zhí)行; if r != 0 && atomic.AddInt32(&rw.readerWait, r) != 0 { // 將寫操作掛起 runtime_SemacquireMutex(&rw.writerSem, false, 0) } }
Unlock()方法
Unlock
方法用于寫操作釋放鎖,其操作如下:
將readerCount
更新為正數(shù),表示當(dāng)前不存在活躍的寫操作;
如果更新后的readerCount
大于0,表示當(dāng)前寫操作阻塞了readerCount
個(gè)讀操作,需要將所有被阻塞的讀操作都喚醒;
將w
互斥鎖釋放,允許其他寫操作執(zhí)行;
func (rw *RWMutex) Unlock() { // 將readerCount更新為正數(shù),從0開始計(jì)數(shù) r := atomic.AddInt32(&rw.readerCount, rwmutexMaxReaders) if r >= rwmutexMaxReaders { throw("sync: Unlock of unlocked RWMutex") } // 喚醒所有等待寫操作的讀操作 for i := 0; i < int(r); i++ { runtime_Semrelease(&rw.readerSem, false, 0) } // 釋放w互斥鎖,允許其他寫操作進(jìn)入 rw.w.Unlock() }
RLock()方法
RLock
方法用于讀操作獲取鎖,其操作如下:
- 原子更新
readerCount+1
; - 如果當(dāng)前存在寫操作
atomic.AddInt32(&rw.readerCount, 1) < 0
,讀操作進(jìn)入阻塞狀態(tài);
func (rw *RWMutex) RLock() { // 原子更新readerCount+1 // 1. readerCount+1為負(fù)數(shù)時(shí),表示當(dāng)前存在寫操作;讀操作需要等待寫操作執(zhí)行完,才能被執(zhí)行 // 2. readerCount+1不為負(fù)數(shù)時(shí),表示當(dāng)前不存在寫操作,讀操作可以執(zhí)行 if atomic.AddInt32(&rw.readerCount, 1) < 0 { // 將讀操作掛起 runtime_SemacquireMutex(&rw.readerSem, false, 0) } }
RUnlock()方法
RUnlock
方法用于讀操作釋放鎖,其操作如下:
原子更新readerCount-1
;
如果當(dāng)前讀操作阻塞了寫操作atomic.AddInt32(&rw.readerCount, -1)<0
,原子更新readerWait-1
;
當(dāng)readerWait
為0時(shí),表示阻塞寫操作的所有讀操作都執(zhí)行完了,喚醒寫操作;
func (rw *RWMutex) RUnlock() { // 原子更新readerCount-1 // 當(dāng)readerCount-1為負(fù)時(shí),表示當(dāng)前讀操作阻塞了寫操作,需要進(jìn)行readerWait的更新 if r := atomic.AddInt32(&rw.readerCount, -1); r < 0 { rw.rUnlockSlow(r) } } func (rw *RWMutex) rUnlockSlow(r int32) { if r+1 == 0 || r+1 == -rwmutexMaxReaders { throw("sync: RUnlock of unlocked RWMutex") } // 原子操作readerWait-1 // 當(dāng)readerWait-1為0時(shí),表示導(dǎo)致寫操作阻塞的所有讀操作都執(zhí)行完,將寫操作喚醒 if atomic.AddInt32(&rw.readerWait, -1) == 0 { // 喚醒讀操作 runtime_Semrelease(&rw.writerSem, false, 1) } }
到此這篇關(guān)于Go并發(fā)之RWMutex的源碼解析詳解的文章就介紹到這了,更多相關(guān)Go RWMutex內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Golang設(shè)計(jì)模式工廠模式實(shí)戰(zhàn)寫法示例詳解
這篇文章主要為大家介紹了Golang 工廠模式實(shí)戰(zhàn)寫法示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08解決golang處理http response碰到的問題和需要注意的點(diǎn)
這篇文章主要介紹了解決golang處理http response碰到的問題和需要注意的點(diǎn),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-12-12Go語言string,int,int64 ,float之間類型轉(zhuǎn)換方法
Go語言中int類型和string類型都是屬于基本數(shù)據(jù)類型,兩種類型的轉(zhuǎn)化都非常簡(jiǎn)單。下面通過本文給大家分享Go語言string,int,int64 ,float之間類型轉(zhuǎn)換方法,感興趣的朋友一起看看吧2017-07-07Go使用defer函數(shù)要注意的幾個(gè)點(diǎn)
這篇文章主要介紹了Go使用defer函數(shù)要注意的幾個(gè)點(diǎn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-12-12