溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

如何理解golang里面的讀寫鎖實(shí)現(xiàn)與核心原理

發(fā)布時(shí)間:2021-10-12 11:11:27 來(lái)源:億速云 閱讀:151 作者:柒染 欄目:云計(jì)算

如何理解golang里面的讀寫鎖實(shí)現(xiàn)與核心原理,很多新手對(duì)此不是很清楚,為了幫助大家解決這個(gè)難題,下面小編將為大家詳細(xì)講解,有這方面需求的人可以來(lái)學(xué)習(xí)下,希望你能有所收獲。

基礎(chǔ)筑基

讀寫鎖的特點(diǎn)

讀寫鎖區(qū)別與互斥鎖的主要區(qū)別就是讀鎖之間是共享的,多個(gè)goroutine可以同時(shí)加讀鎖,但是寫鎖與寫鎖、寫鎖與讀鎖之間則是互斥的

寫鎖饑餓問(wèn)題

因?yàn)樽x鎖是共享的,所以如果當(dāng)前已經(jīng)有讀鎖,那后續(xù)goroutine繼續(xù)加讀鎖正常情況下是可以加鎖成功,但是如果一直有讀鎖進(jìn)行加鎖,那嘗試加寫鎖的goroutine則可能會(huì)長(zhǎng)期獲取不到鎖,這就是因?yàn)樽x鎖而導(dǎo)致的寫鎖饑餓問(wèn)題

基于高低位與等待隊(duì)列的實(shí)現(xiàn)

如何理解golang里面的讀寫鎖實(shí)現(xiàn)與核心原理 在說(shuō)golang之前介紹一種JAVA里面的實(shí)現(xiàn),在JAVA中ReentrantReadWriteLock實(shí)現(xiàn)采用一個(gè)state的高低位來(lái)進(jìn)行讀寫鎖的計(jì)數(shù),其中高16位存儲(chǔ)讀的計(jì)數(shù),低16位存儲(chǔ)寫的計(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)

golang的讀寫鎖的實(shí)現(xiàn)

成員變量

如何理解golang里面的讀寫鎖實(shí)現(xiàn)與核心原理

結(jié)構(gòu)體

type RWMutex struct {
	w           Mutex  // held if there are pending writers
	writerSem   uint32 // 用于writer等待讀完成排隊(duì)的信號(hào)量
	readerSem   uint32 // 用于reader等待寫完成排隊(duì)的信號(hào)量
	readerCount int32  // 讀鎖的計(jì)數(shù)器
	readerWait  int32  // 等待讀鎖釋放的數(shù)量
}

寫鎖計(jì)數(shù)

讀寫鎖中允許加讀鎖的最大數(shù)量是4294967296,在go里面對(duì)寫鎖的計(jì)數(shù)采用了負(fù)值進(jìn)行,通過(guò)遞減最大允許加讀鎖的數(shù)量從而進(jìn)行寫鎖對(duì)讀鎖的搶占

const rwmutexMaxReaders = 1 << 30

讀鎖實(shí)現(xiàn)

讀鎖加鎖邏輯

如何理解golang里面的讀寫鎖實(shí)現(xiàn)與核心原理

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))
	}
}

讀鎖釋放邏輯

如何理解golang里面的讀寫鎖實(shí)現(xiàn)與核心原理

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()
	}
}

寫鎖實(shí)現(xiàn)

加寫鎖實(shí)現(xiàn)

如何理解golang里面的讀寫鎖實(shí)現(xiàn)與核心原理

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)寫鎖對(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))
	}
}

釋放寫鎖

如何理解golang里面的讀寫鎖實(shí)現(xiàn)與核心原理

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()
	}
}

關(guān)鍵核心機(jī)制

寫鎖對(duì)讀鎖的搶占

加寫鎖的搶占

	// 在加寫鎖的時(shí)候通過(guò)將readerCount遞減最大允許加讀鎖的數(shù)量,來(lái)實(shí)現(xiàn)對(duì)加讀鎖的搶占
	r := atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders) + rwmutexMaxReaders

加讀鎖的搶占檢測(cè)

// 如果沒(méi)有寫鎖的情況下讀鎖的readerCount進(jìn)行Add后一定是一個(gè)>0的數(shù)字,這里通過(guò)檢測(cè)值為負(fù)數(shù)
//就實(shí)現(xiàn)了讀鎖對(duì)寫鎖搶占的檢測(cè)
if atomic.AddInt32(&rw.readerCount, 1) < 0 {
		// A writer is pending, wait for it.
		runtime_SemacquireMutex(&rw.readerSem, false)
	}

寫鎖搶占讀鎖后后續(xù)的讀鎖就會(huì)加鎖失敗,但是如果想加寫鎖成功還要繼續(xù)對(duì)已經(jīng)加讀鎖成功的進(jìn)行等待

	if r != 0 && atomic.AddInt32(&rw.readerWait, r) != 0 {
        // 寫鎖發(fā)現(xiàn)需要等待的讀鎖釋放的數(shù)量不為0,就自己自己去休眠了
		runtime_SemacquireMutex(&rw.writerSem, false)
	}

寫鎖既然休眠了,則必定要有一種喚醒機(jī)制其實(shí)就是每次釋放鎖的時(shí)候,當(dāng)檢查到有加寫鎖的情況下,就遞減readerWait,并由最后一個(gè)釋放reader lock的goroutine來(lái)實(shí)現(xiàn)喚醒寫鎖

		if atomic.AddInt32(&rw.readerWait, -1) == 0 {
			// The last reader unblocks the writer.
			runtime_Semrelease(&rw.writerSem, false)
		}

寫鎖的公平性

在加寫鎖的時(shí)候必須先進(jìn)行mutex的加鎖,而mutex本身在普通模式下是非公平的,只有在饑餓模式下才是公平的

	rw.w.Lock()

寫鎖與讀鎖的公平性

在加讀鎖和寫鎖的工程中都使用atomic.AddInt32來(lái)進(jìn)行遞增,而該指令在底層是會(huì)通過(guò)LOCK來(lái)進(jìn)行CPU總線加鎖的,因此多個(gè)CPU同時(shí)執(zhí)行readerCount其實(shí)只會(huì)有一個(gè)成功,從這上面看其實(shí)是寫鎖與讀鎖之間是相對(duì)公平的,誰(shuí)先達(dá)到誰(shuí)先被CPU調(diào)度執(zhí)行,進(jìn)行LOCK鎖cache line成功,誰(shuí)就加成功鎖

可見(jiàn)性與原子性問(wèn)題

在并發(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ò)程的不可分割

底層實(shí)現(xiàn)的CPU指令

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)注億速云行業(yè)資訊頻道,感謝您對(duì)億速云的支持。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI