Golang的讀寫鎖的實現
結構體
type RWMutex struct { w Mutex // held if there are pending writers writerSem uint32 // 用於writer等待讀完成排隊的信號量 readerSem uint32 // 用於reader等待寫完成排隊的信號量 readerCount int32 // 讀鎖的計數器 readerWait int32 // 等待讀鎖釋放的數量 }
讀寫鎖中允許加讀鎖的最大數量是4294967296,在go里面對寫鎖的計數采用了負值進行,通過遞減最大允許加讀鎖的數量從而進行寫鎖對讀鎖的搶占
const rwmutexMaxReaders = 1 << 30
讀鎖加鎖實現
func (rw *RWMutex) RLock() { if race.Enabled { _ = rw.w.state race.Disable() } // 累加reader計數器,如果小於0則表明有writer正在等待 if atomic.AddInt32(&rw.readerCount, 1) < 0 { // 當前有writer正在等待讀鎖,讀鎖就加入排隊 runtime_SemacquireMutex(&rw.readerSem, false) } if race.Enabled { race.Enable() race.Acquire(unsafe.Pointer(&rw.readerSem)) } }
讀鎖釋放實現
func (rw *RWMutex) RUnlock() { if race.Enabled { _ = rw.w.state race.ReleaseMerge(unsafe.Pointer(&rw.writerSem)) race.Disable() } // 如果小於0,則表明當前有writer正在等待 if r := atomic.AddInt32(&rw.readerCount, -1); r < 0 { if r+1 == 0 || r+1 == -rwmutexMaxReaders { race.Enable() throw("sync: RUnlock of unlocked RWMutex") } // 將等待reader的計數減1,證明當前是已經有一個讀的,如果值==0,則進行喚醒等待的 if atomic.AddInt32(&rw.readerWait, -1) == 0 { // The last reader unblocks the writer. runtime_Semrelease(&rw.writerSem, false) } } if race.Enabled { race.Enable() } }
加寫鎖實現
func (rw *RWMutex) Lock() { if race.Enabled { _ = rw.w.state race.Disable() } // 首先獲取mutex鎖,同時多個goroutine只有一個可以進入到下面的邏輯 rw.w.Lock() // 對readerCounter進行進行搶占,通過遞減rwmutexMaxReaders允許最大讀的數量 // 來實現寫鎖對讀鎖的搶占 r := atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders) + rwmutexMaxReaders // 記錄需要等待多少個reader完成,如果發現不為0,則表明當前有reader正在讀取,當前goroutine // 需要進行排隊等待 if r != 0 && atomic.AddInt32(&rw.readerWait, r) != 0 { runtime_SemacquireMutex(&rw.writerSem, false) } if race.Enabled { race.Enable() race.Acquire(unsafe.Pointer(&rw.readerSem)) race.Acquire(unsafe.Pointer(&rw.writerSem)) } }
釋放寫鎖實現
func (rw *RWMutex) Unlock() { if race.Enabled { _ = rw.w.state race.Release(unsafe.Pointer(&rw.readerSem)) race.Disable() } // 將reader計數器復位,上面減去了一個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) } // 釋放mutex rw.w.Unlock() if race.Enabled { race.Enable() } }
Golang讀寫鎖底層原理
在加讀鎖和寫鎖的工程中都使用atomic.AddInt32來進行遞增,而該指令在底層是會通過LOCK來進行CPU總線加鎖的,因此多個CPU同時執行readerCount其實只會有一個成功,從這上面看其實是寫鎖與讀鎖之間是相對公平的,誰先達到誰先被CPU調度執行,進行LOCK鎖cache line成功,誰就加成功鎖
底層實現的CPU指令
底層的2條指令,通過LOCK指令配合CPU的MESI協議,實現可見性和內存屏障,同時通過XADDL則用來保證原子性,從而解決可見性與原子性問題
// atomic/asm_amd64.s TEXT runtime∕internal∕atomic·Xadd(SB) LOCK XADDL AX, 0(BX)
可見性與內存屏障、原子性, 其中可見性通常是指在cpu多級緩存下如何保證緩存的一致性,即在一個CPU上修改了了某個數據在其他的CPU上不會繼續讀取舊的數據,內存屏障通常是為了CPU為了提高流水線性能,而對指令進行重排序而來,而原子性則是指的執行某個操作的過程的不可分割