如何理解golang里面的讀寫(xiě)鎖實(shí)現(xiàn)與核心原理,很多新手對(duì)此不是很清楚,為了幫助大家解決這個(gè)難題,下面小編將為大家詳細(xì)講解,有這方面需求的人可以來(lái)學(xué)習(xí)下,希望你能有所收獲。
創(chuàng)新互聯(lián)建站服務(wù)項(xiàng)目包括慈利網(wǎng)站建設(shè)、慈利網(wǎng)站制作、慈利網(wǎng)頁(yè)制作以及慈利網(wǎng)絡(luò)營(yíng)銷(xiāo)策劃等。多年來(lái),我們專(zhuān)注于互聯(lián)網(wǎng)行業(yè),利用自身積累的技術(shù)優(yōu)勢(shì)、行業(yè)經(jīng)驗(yàn)、深度合作伙伴關(guān)系等,向廣大中小型企業(yè)、政府機(jī)構(gòu)等提供互聯(lián)網(wǎng)行業(yè)的解決方案,慈利網(wǎng)站推廣取得了明顯的社會(huì)效益與經(jīng)濟(jì)效益。目前,我們服務(wù)的客戶以成都為中心已經(jīng)輻射到慈利省份的部分城市,未來(lái)相信會(huì)繼續(xù)擴(kuò)大服務(wù)區(qū)域并繼續(xù)獲得客戶的支持與信任!
讀寫(xiě)鎖區(qū)別與互斥鎖的主要區(qū)別就是讀鎖之間是共享的,多個(gè)goroutine可以同時(shí)加讀鎖,但是寫(xiě)鎖與寫(xiě)鎖、寫(xiě)鎖與讀鎖之間則是互斥的
因?yàn)樽x鎖是共享的,所以如果當(dāng)前已經(jīng)有讀鎖,那后續(xù)goroutine繼續(xù)加讀鎖正常情況下是可以加鎖成功,但是如果一直有讀鎖進(jìn)行加鎖,那嘗試加寫(xiě)鎖的goroutine則可能會(huì)長(zhǎng)期獲取不到鎖,這就是因?yàn)樽x鎖而導(dǎo)致的寫(xiě)鎖饑餓問(wèn)題
在說(shuō)golang之前介紹一種JAVA里面的實(shí)現(xiàn),在JAVA中ReentrantReadWriteLock實(shí)現(xiàn)采用一個(gè)state的高低位來(lái)進(jìn)行讀寫(xiě)鎖的計(jì)數(shù),其中高16位存儲(chǔ)讀的計(jì)數(shù),低16位存儲(chǔ)寫(xiě)的計(jì)數(shù),并配合一個(gè)AQS來(lái)實(shí)現(xiàn)排隊(duì)等待機(jī)制,同時(shí)AQS中的每個(gè)waiter都會(huì)有一個(gè)status,用來(lái)標(biāo)識(shí)自己的狀態(tài)
type RWMutex struct { w Mutex // held if there are pending writers writerSem uint32 // 用于writer等待讀完成排隊(duì)的信號(hào)量 readerSem uint32 // 用于reader等待寫(xiě)完成排隊(duì)的信號(hào)量 readerCount int32 // 讀鎖的計(jì)數(shù)器 readerWait int32 // 等待讀鎖釋放的數(shù)量 }
讀寫(xiě)鎖中允許加讀鎖的最大數(shù)量是4294967296,在go里面對(duì)寫(xiě)鎖的計(jì)數(shù)采用了負(fù)值進(jìn)行,通過(guò)遞減最大允許加讀鎖的數(shù)量從而進(jìn)行寫(xiě)鎖對(duì)讀鎖的搶占
const rwmutexMaxReaders = 1 << 30
func (rw *RWMutex) RLock() { if race.Enabled { _ = rw.w.state race.Disable() } // 累加reader計(jì)數(shù)器,如果小于0則表明有writer正在等待 if atomic.AddInt32(&rw.readerCount, 1) < 0 { // 當(dāng)前有writer正在等待讀鎖,讀鎖就加入排隊(duì) 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,則表明當(dāng)前有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的計(jì)數(shù)減1,證明當(dāng)前是已經(jīng)有一個(gè)讀的,如果值==0,則進(jìn)行喚醒等待的 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鎖,同時(shí)多個(gè)goroutine只有一個(gè)可以進(jìn)入到下面的邏輯 rw.w.Lock() // 對(duì)readerCounter進(jìn)行進(jìn)行搶占,通過(guò)遞減rwmutexMaxReaders允許最大讀的數(shù)量 // 來(lái)實(shí)現(xiàn)寫(xiě)鎖對(duì)讀鎖的搶占 r := atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders) + rwmutexMaxReaders // 記錄需要等待多少個(gè)reader完成,如果發(fā)現(xiàn)不為0,則表明當(dāng)前有reader正在讀取,當(dāng)前goroutine // 需要進(jìn)行排隊(duì)等待 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計(jì)數(shù)器復(fù)位,上面減去了一個(gè)rwmutexMaxReaders現(xiàn)在再重新加回去即可復(fù)位 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() } }
加寫(xiě)鎖的搶占
// 在加寫(xiě)鎖的時(shí)候通過(guò)將readerCount遞減最大允許加讀鎖的數(shù)量,來(lái)實(shí)現(xiàn)對(duì)加讀鎖的搶占 r := atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders) + rwmutexMaxReaders
加讀鎖的搶占檢測(cè)
// 如果沒(méi)有寫(xiě)鎖的情況下讀鎖的readerCount進(jìn)行Add后一定是一個(gè)>0的數(shù)字,這里通過(guò)檢測(cè)值為負(fù)數(shù) //就實(shí)現(xiàn)了讀鎖對(duì)寫(xiě)鎖搶占的檢測(cè) if atomic.AddInt32(&rw.readerCount, 1) < 0 { // A writer is pending, wait for it. runtime_SemacquireMutex(&rw.readerSem, false) }
寫(xiě)鎖搶占讀鎖后后續(xù)的讀鎖就會(huì)加鎖失敗,但是如果想加寫(xiě)鎖成功還要繼續(xù)對(duì)已經(jīng)加讀鎖成功的進(jìn)行等待
if r != 0 && atomic.AddInt32(&rw.readerWait, r) != 0 { // 寫(xiě)鎖發(fā)現(xiàn)需要等待的讀鎖釋放的數(shù)量不為0,就自己自己去休眠了 runtime_SemacquireMutex(&rw.writerSem, false) }
寫(xiě)鎖既然休眠了,則必定要有一種喚醒機(jī)制其實(shí)就是每次釋放鎖的時(shí)候,當(dāng)檢查到有加寫(xiě)鎖的情況下,就遞減readerWait,并由最后一個(gè)釋放reader lock的goroutine來(lái)實(shí)現(xiàn)喚醒寫(xiě)鎖
if atomic.AddInt32(&rw.readerWait, -1) == 0 { // The last reader unblocks the writer. runtime_Semrelease(&rw.writerSem, false) }
在加寫(xiě)鎖的時(shí)候必須先進(jìn)行mutex的加鎖,而mutex本身在普通模式下是非公平的,只有在饑餓模式下才是公平的
rw.w.Lock()
在加讀鎖和寫(xiě)鎖的工程中都使用atomic.AddInt32來(lái)進(jìn)行遞增,而該指令在底層是會(huì)通過(guò)LOCK來(lái)進(jìn)行CPU總線加鎖的,因此多個(gè)CPU同時(shí)執(zhí)行readerCount其實(shí)只會(huì)有一個(gè)成功,從這上面看其實(shí)是寫(xiě)鎖與讀鎖之間是相對(duì)公平的,誰(shuí)先達(dá)到誰(shuí)先被CPU調(diào)度執(zhí)行,進(jìn)行LOCK鎖cache line成功,誰(shuí)就加成功鎖
在并發(fā)場(chǎng)景中特別是JAVA中通常會(huì)提到并發(fā)里面的兩個(gè)問(wèn)題:可見(jiàn)性與內(nèi)存屏障、原子性, 其中可見(jiàn)性通常是指在cpu多級(jí)緩存下如何保證緩存的一致性,即在一個(gè)CPU上修改了了某個(gè)數(shù)據(jù)在其他的CPU上不會(huì)繼續(xù)讀取舊的數(shù)據(jù),內(nèi)存屏障通常是為了CPU為了提高流水線性能,而對(duì)指令進(jìn)行重排序而來(lái),而原子性則是指的執(zhí)行某個(gè)操作的過(guò)程的不可分割
go里面并沒(méi)有volatile這種關(guān)鍵字,那如何能保證上面的AddInt32這個(gè)操作可以滿足上面的兩個(gè)問(wèn)題呢, 其實(shí)關(guān)鍵就在于底層的2條指令,通過(guò)LOCK指令配合CPU的MESI協(xié)議,實(shí)現(xiàn)可見(jiàn)性和內(nèi)存屏障,同時(shí)通過(guò)XADDL則用來(lái)保證原子性,從而解決上面提到的可見(jiàn)性與原子性問(wèn)題
// atomic/asm_amd64.s TEXT runtime∕internal∕atomic·Xadd(SB) LOCK XADDL AX, 0(BX)
看完上述內(nèi)容是否對(duì)您有幫助呢?如果還想對(duì)相關(guān)知識(shí)有進(jìn)一步的了解或閱讀更多相關(guān)文章,請(qǐng)關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,感謝您對(duì)創(chuàng)新互聯(lián)的支持。