Go语言 读写锁互斥锁原理剖析(1)
我們在多協程操作時,有種場景是讀操作次數遠遠大于寫操作,這個時候,我們就會考慮用到讀寫鎖。
讀寫鎖
讀寫鎖(百科)定義:是一種特殊的的自旋鎖,它把對共享資源的訪問者劃分成讀者和寫者,讀者只對共享資源進行讀訪問,寫者則需要對共享資源進行寫操作。
在Go語言中、sync標準庫定義了RWMutex結構代表讀寫鎖,該結構在rwmutex.go中,RWMutex繼承于Locker接口,其實是互斥鎖的改進版,為什么這樣說呢,看完下文就會清楚了。
讀寫鎖特征:
讀鎖之間是共享的,多協程可以同時加讀鎖,寫鎖與讀鎖之間是互斥的。
源碼基于:go version go1.13.4 windows/amd64。
Locker接口
type Locker interface {Lock()Unlock() }讀寫鎖結構體定義
type RWMutex struct {w Mutex // 互斥鎖,寫鎖協程獲取該鎖后,其他寫鎖處于阻塞等待writerSem uint32 // 寫入等待信號量,最后一個讀取協程釋放鎖時會釋放信號量readerSem uint32 // 讀取等待信號量,持有寫鎖協程釋放后會釋放信號量readerCount int32 // 讀鎖的個數readerWait int32 // 寫操作時,需要等待讀操作的個數 }我們看到RWMutex讀寫鎖里面包含:
再看常量rwmutexMaxReaders,定義的是最大讀鎖數量
// rwmutexMaxReaders:支持1073741824個鎖 const rwmutexMaxReaders = 1 << 30 sync.RWMutex所有方法如下圖所示讀寫鎖的核心方法:其主要核心的有Lock、Unlock、RLock和RUnlock四個方法。
func (*RWMutex) Lock // 獲取寫鎖操作 func (*RWMutex) Unlock // 解除寫鎖操作 func (*RWMutex) RLock // 獲取讀鎖操作 func (*RWMutex) RUnlock // 解除讀鎖操作 RLocker() Locker // 返回Locker對象?
RLocker,返回一個Locker對象
// 返回一個Locker對象 func (rw *RWMutex) RLocker() Locker {return (*rlocker)(rw) }Lock,獲取寫鎖操作
func (rw *RWMutex) Lock() {// ①、競爭鎖時檢測if race.Enabled {_ = rw.w.staterace.Disable()}// 先獲取Mutex互斥鎖,多協程時只能有一個協程拿到鎖rw.w.Lock()// ②、寫鎖時,會將readerCount減去rwmutexMaxReaders,// 設置需要等待釋放的讀鎖的數量,如有則掛起獲取讀鎖的協程r := atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders) + rwmutexMaxReadersif 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))} }①寫鎖操作只能被一個寫協程拿到,競爭檢測后進行互斥鎖上鎖Mutex.lock,,多協程時只能有一個協程拿到鎖
②寫鎖時,readerCount減去rwmutexMaxReaders,readerCount會變成很大的負數,讀鎖時readerCount會+1
③如果readerCount不等于0,協程獲取到讀鎖,如果滿足上鎖條件時,會調用runtime_SemacquireMutex,如果先進來的會在隊列前面阻塞等待的信號量為writerSem,進入隊列等待。
Unlock,解除寫鎖操作
func (rw *RWMutex) Unlock() {// ①競爭檢測if race.Enabled {_ = rw.w.staterace.Release(unsafe.Pointer(&rw.readerSem))race.Disable()}// ②需要回復readerCount,上鎖的時候減了常量rwmutexMaxReaders,這里再加回來r := atomic.AddInt32(&rw.readerCount, rwmutexMaxReaders)// ③對未被寫鎖定的讀寫鎖進行寫解鎖,會報錯if r >= rwmutexMaxReaders {race.Enable()throw("sync: Unlock of unlocked RWMutex")}// ④呼喚阻塞的讀操作for i := 0; i < int(r); i++ {runtime_Semrelease(&rw.readerSem, false, 0)}// ⑤釋放鎖,如果有其他阻塞寫操作,在此喚醒rw.w.Unlock()if race.Enabled {race.Enable()} }①解除寫鎖操作時,競爭檢測
②需要回復readerCount,上鎖的時候減了常量rwmutexMaxReaders,這里再加回來,加完之后如果大于等于rwmutexMaxReaders
③對未被寫鎖定的讀寫鎖進行寫解鎖,會拋異常
④呼喚阻塞的讀操作
⑤釋放鎖,如果有其他阻塞寫操作,在此喚醒
RLock,獲取讀鎖操作
func (rw *RWMutex) RLock() {if race.Enabled {_ = rw.w.staterace.Disable()}// ①讀鎖時readerCount+1操作,當值小于0時會調用runtime_SemacquireMutex,表明寫鎖操作正在等待if atomic.AddInt32(&rw.readerCount, 1) < 0 {// 當寫鎖操作時,讀鎖也會阻塞等待runtime_SemacquireMutex(&rw.readerSem, false, 0)}if race.Enabled {race.Enable()race.Acquire(unsafe.Pointer(&rw.readerSem))} }①①讀鎖時readerCount+1操作,當值小于0時會調用runtime_SemacquireMutex,表明寫鎖操作正在等待
②當寫鎖操作時,讀鎖也會阻塞等待,讀鎖加入等待隊列
RUnlock,解除讀鎖操作
func (rw *RWMutex) RUnlock() {if race.Enabled {_ = rw.w.staterace.ReleaseMerge(unsafe.Pointer(&rw.writerSem))race.Disable()}// ①直接將readerCount-1操作if r := atomic.AddInt32(&rw.readerCount, -1); r < 0 {// ②如果r小于0,說明有協程正在獲取寫操作鎖,// 當readerWait減到0時說明沒有協程持有寫操作鎖,// 就通過信息號wrirerSem通知等待的協程來爭搶鎖rw.rUnlockSlow(r)}if race.Enabled {race.Enable()} }①解除讀鎖,直接對readerCount-1操作,這里就是把之前+1操作的再減回來
②如果r小于0,說明有協程正在獲取寫操作鎖,當readerWait減到0時說明沒有協程持有寫操作鎖,就通過信息號wrirerSem通知等待的協程來爭搶鎖
總結
- 寫鎖操作時,對讀寫鎖進行讀鎖定和寫鎖定,都會阻塞,讀鎖與寫鎖之間是互斥的。
- 讀鎖操作時,對讀寫鎖進行寫鎖定,會阻塞,加讀鎖時不會阻塞。
- 對未被上鎖的讀|寫鎖進行解除鎖定操作,會Panic。
- 解除寫鎖操作時,同時會喚醒所有阻塞的讀鎖協程。
- 解除讀鎖操作時,會喚醒一個因寫鎖操作而被阻塞的協程。
- 讀鎖存在的時候,同時出現讀鎖和寫鎖操作,優先獲取寫鎖。
- 同時2個協程去爭搶讀鎖和寫鎖時,都有機會搶成功。
- 寫鎖存在時,同時開啟2個協程去爭搶讀鎖和寫鎖,優先獲得讀鎖。
本文為原創文章,出自guichenglin,轉載請粘貼源鏈接,如果未經允許轉發后果自負。
總結
以上是生活随笔為你收集整理的Go语言 读写锁互斥锁原理剖析(1)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Nginx反向代理+Go服务实践
- 下一篇: Go语言 读写锁互斥锁原理剖析(2)