引用
建議閱讀
互斥鎖有兩種狀態(tài):正常狀態(tài)和饑餓狀態(tài)。
在正常狀態(tài)下,所有等待鎖的goroutine按照FIFO順序等待。喚醒的goroutine不會(huì)直接擁有鎖,而是會(huì)和新請(qǐng)求鎖的goroutine競(jìng)爭(zhēng)鎖的擁有。新請(qǐng)求鎖的goroutine具有優(yōu)勢(shì):它正在CPU上執(zhí)行,而且可能有好幾個(gè),所以剛剛喚醒的goroutine有很大可能在鎖競(jìng)爭(zhēng)中失敗。在這種情況下,這個(gè)被喚醒的goroutine會(huì)加入到等待隊(duì)列的前面。 如果一個(gè)等待的goroutine超過(guò)1ms沒(méi)有獲取鎖,那么它將會(huì)把鎖轉(zhuǎn)變?yōu)轲囸I模式。
在饑餓模式下,鎖的所有權(quán)將從unlock的gorutine直接交給交給等待隊(duì)列中的第一個(gè)。新來(lái)的goroutine將不會(huì)嘗試去獲得鎖,即使鎖看起來(lái)是unlock狀態(tài), 也不會(huì)去嘗試自旋操作,而是放在等待隊(duì)列的尾部。
如果一個(gè)等待的goroutine獲取了鎖,并且滿足一以下其中的任何一個(gè)條件:(1)它是隊(duì)列中的最后一個(gè);(2)它等待的時(shí)候小于1ms。它會(huì)將鎖的狀態(tài)轉(zhuǎn)換為正常狀態(tài)。
正常狀態(tài)有很好的性能表現(xiàn),饑餓模式也是非常重要的,因?yàn)樗茏柚刮膊垦舆t的現(xiàn)象。
原子操作:指那些不能夠被打斷的操作被稱為原子操作,當(dāng)有一個(gè)CPU在訪問(wèn)這塊內(nèi)容addr時(shí),其他CPU就不能訪問(wèn)。
CAS:比較及交換,其實(shí)也屬于原子操作,但它是非阻塞的,所以在被操作值被頻繁變更的情況下,CAS操作并不那么容易成功,不得不利用for循環(huán)以進(jìn)行多次嘗試。
自旋鎖(spinlock)
自旋鎖是指當(dāng)一個(gè)線程在獲取鎖的時(shí)候,如果鎖已經(jīng)被其他線程獲取,那么該線程將循環(huán)等待,然后不斷地判斷是否能夠被成功獲取,知直到獲取到鎖才會(huì)退出循環(huán)。獲取鎖的線程一直處于活躍狀態(tài) Golang中的自旋鎖用來(lái)實(shí)現(xiàn)其他類型的鎖,與互斥鎖類似,不同點(diǎn)在于,它不是通過(guò)休眠來(lái)使進(jìn)程阻塞,而是在獲得鎖之前一直處于活躍狀態(tài)(自旋)。
Mutex結(jié)構(gòu)
type Mutex struct {
state int32 // 表示鎖當(dāng)前的狀態(tài)
sema uint32 // 信號(hào)量 用于向處于Gwaitting的G發(fā)送信號(hào)
}
狀態(tài)值[2]
mutexLocked
值為 1,第一位為 1,表示 mutex 已經(jīng)被加鎖。根據(jù) mutex.state & mutexLocked 的結(jié)果來(lái)判斷 mutex 的狀態(tài):該位為 1 表示已加鎖,0 表示未加鎖。mutexWoken
值為 2,第二位為 1,表示 mutex 是否被喚醒。根據(jù) mutex.state & mutexWoken 的結(jié)果判斷 mutex 是否被喚醒:該位為 1 表示已被喚醒,0 表示未被喚醒。mutexStarving
值為 4,第三位為 1,表示 mutex 是否處于饑餓模式。根據(jù) mutex.state & mutexWoken 的結(jié)果判斷 mutex 是否處于饑餓模式:該位為 1 表示處于饑餓模式,0 表示正常模式。mutexWaiterShift
值為 3,表示 mutex.state 右移 3 位后即為等待的 goroutine 的數(shù)量。starvationThresholdNs
值為 1000000 納秒,即 1ms,表示將 mutex 切換到饑餓模式的等待時(shí)間閾值。
工作模式[2]
正常模式下
等待者以 FIFO 的順序排隊(duì)來(lái)獲取鎖,但被喚醒的等待者發(fā)現(xiàn)并沒(méi)有獲取到 mutex,并且還要與新到達(dá)的 goroutine 們競(jìng)爭(zhēng) mutex 的所有權(quán)。新到達(dá)的 goroutine 們有一個(gè)優(yōu)勢(shì) —— 它們已經(jīng)運(yùn)行在 CPU 上且可能數(shù)量很多,所以一個(gè)醒來(lái)的等待者有很大可能會(huì)獲取不到鎖。在這種情況下它處在等待隊(duì)列的前面。如果一個(gè) goroutine 等待 mutex 釋放的時(shí)間超過(guò) 1ms,它就會(huì)將 mutex 切換到饑餓模式。在饑餓模式下
mutex 的所有權(quán)直接從對(duì) mutex 執(zhí)行解鎖的 goroutine 傳遞給等待隊(duì)列前面的等待者。新到達(dá)的 goroutine 們不要嘗試去獲取 mutex,即使它看起來(lái)是在解鎖狀態(tài),也不要試圖自旋(等也白等,在饑餓模式下是不會(huì)給你的),而是自己乖乖到等待隊(duì)列的尾部排隊(duì)去。
如果一個(gè)等待者獲得 mutex 的所有權(quán),并且看到以下兩種情況中的任一種:1) 它是等待隊(duì)列中的最后一個(gè),或者 2) 它等待的時(shí)間少于 1ms,它便將 mutex 切換回正常操作模式。
正常模式有更好地性能,因?yàn)橐粋€(gè) goroutine 可以連續(xù)獲得好幾次 mutex,即使有阻塞的等待者。而饑餓模式可以有效防止出現(xiàn)位于等待隊(duì)列尾部的等待者一直無(wú)法獲取到 mutex 的情況。