Mutext兩種模式
正常模式和饑餓模式蟀淮。一開始默認(rèn)處于正常模式最住。在正常模式中,每個(gè)新加入競(jìng)爭(zhēng)鎖行列的協(xié)程都會(huì)直接參與到鎖的競(jìng)爭(zhēng)當(dāng)中來怠惶,而處于饑餓模式時(shí)涨缚,所有所有新進(jìn)入的協(xié)程都會(huì)直接被放入等待隊(duì)列中掛起,直到其所在隊(duì)列之前的協(xié)程全部執(zhí)行完畢策治。在正常模式中協(xié)程的掛起等待時(shí)間如果大于某個(gè)值脓魏,就會(huì)進(jìn)入饑餓模式。
type Mutex struct {
state int32
sema uint32
}
其中览妖,state用來保存mutex的狀態(tài)量轧拄,低一位表示是否上鎖,低二位表示當(dāng)前鎖對(duì)象是否被喚醒讽膏,低三位表示該鎖是否處于饑餓狀態(tài),而其余位表示當(dāng)前正被該鎖阻塞的協(xié)程數(shù)拄丰。而sema則是作為信號(hào)量來作為阻塞的依據(jù)府树。
state: |32|31|...| |3|2|1|
\__________/ | | |
| | | |
| | | mutex的占用狀態(tài)(1被占用俐末,0可用)
| | |
| | mutex的當(dāng)前goroutine是否被喚醒
| |
| 饑餓位,0正常奄侠,1饑餓
|
等待喚醒以嘗試鎖定的goroutine的計(jì)數(shù)卓箫,0表示沒有等待者
上述很多的運(yùn)算都是位運(yùn)算,原因是:鎖在同一時(shí)刻可能具備多個(gè)狀態(tài)垄潮,還有一個(gè)原因就是state字段 只有 低位的三位是用來控制狀態(tài)的烹卒,而其他的位都是用來做計(jì)數(shù)的,所以不能直接賦值操作弯洗,而是用了位運(yùn)算賦值旅急。
正常模式
等待的goroutines按照 FIFO(先進(jìn)先出)順序排隊(duì),但是goroutine被喚醒之后并不能立即得到mutex鎖牡整,它需要與新到達(dá)的goroutine爭(zhēng)奪mutex鎖藐吮。 因?yàn)樾碌竭_(dá)的goroutine已經(jīng)在CPU上運(yùn)行了,所以被喚醒的goroutine很大概率是爭(zhēng)奪mutex鎖是失敗的逃贝。 出現(xiàn)這樣的情況時(shí)候谣辞,被喚醒的goroutine需要排隊(duì)在隊(duì)列的前面。 如果被喚醒的goroutine有超過1ms沒有獲取到mutex鎖沐扳,那么它就會(huì)變?yōu)?饑餓模式泥从。 在饑餓模式中,mutex鎖直接從解鎖的goroutine交給隊(duì)列前面的goroutine沪摄。新達(dá)到的goroutine也不會(huì)去爭(zhēng)奪mutex鎖(即使沒有鎖歉闰,也不能去自旋),而是到等待隊(duì)列尾部排隊(duì)卓起。
饑餓模式
有一個(gè)goroutine獲取到mutex鎖了和敬,如果它滿足下條件中的任意一個(gè),mutex將會(huì)切換回去正常模式: 1. 是等待隊(duì)列中的最后一個(gè)goroutine 戏阅。2. 它的等待時(shí)間不超過1ms昼弟。 正常模式有更好的性能,因?yàn)間oroutine可以連續(xù)多次獲得mutex鎖奕筐; 饑餓模式需要預(yù)防隊(duì)列尾部goroutine一直無法獲取mutex鎖的問題舱痘。
嘗試獲取mutex的goroutine也有狀態(tài),有可能它是新來的goroutine离赫,也有可能是被喚醒的goroutine, 可能是處于正常狀態(tài)的goroutine, 也有可能是處于饑餓狀態(tài)的goroutine
四個(gè)重要的方法
runtime_canSpin: 在 src/runtime/proc.go 中被實(shí)現(xiàn) sync_runtime_canSpin芭逝; 表示是否可以保守的自旋,golang中自旋鎖并不會(huì)一直自旋下去渊胸,在runtime包中runtime_canSpin方法做了一些限制, 傳遞過來的iter大等于4或者cpu核數(shù)小等于1旬盯,最大邏輯處理器大于1,至少有個(gè)本地的P隊(duì)列,并且本地的P隊(duì)列可運(yùn)行G隊(duì)列為空胖翰。
runtime_doSpin: 在 src/runtime/proc.go 中被實(shí)現(xiàn) sync_runtime_doSpin接剩;表示 會(huì)調(diào)用procyield函數(shù), 該函數(shù)也是匯編語言實(shí)現(xiàn)萨咳。函數(shù)內(nèi)部循環(huán)調(diào)用PAUSE指令懊缺。PAUSE指令什么都不做, 但是會(huì)消耗CPU時(shí)間培他,在執(zhí)行PAUSE指令時(shí)鹃两,CPU不會(huì)對(duì)它做不必要的優(yōu)化。
runtime_SemacquireMutex:在 src/runtime/sema.go 中被實(shí)現(xiàn) sync_runtime_SemacquireMutex舀凛;表示通過信號(hào)量 阻塞當(dāng)前協(xié)程 俊扳。
runtime_Semrelease: 在src/runtime/sema.go 中被實(shí)現(xiàn) sync_runtime_Semrelease。表示通過信號(hào)量解除當(dāng)前協(xié)程阻塞腾降。
func throw(string) // 被定義在 runtime 包中拣度,src/runtime/panic.go 的 sync_throw 方法
// mutex 是一個(gè)互斥鎖
// 零值是沒有被上鎖的互斥鎖。
//
// 首次使用后螃壤,不得復(fù)制互斥鎖抗果。(意思是不能復(fù)制值,可以做成引用復(fù)制)
type Mutex struct {
// 將一個(gè)32位整數(shù)拆分為
// 當(dāng)前阻塞的goroutine數(shù)目(29位)|饑餓狀態(tài)(1位)|喚醒狀態(tài)(1位)|鎖狀態(tài)(1位) 的形式奸晴,來簡(jiǎn)化字段設(shè)計(jì)
state int32
// 信號(hào)量
sema uint32
}
// 鎖接口
type Locker interface {
Lock()
Unlock()
}
const (
// 定義鎖的狀態(tài)
mutexLocked = 1 << iota // 1 表示是否被鎖定 0001 含義:用最后一位表示當(dāng)前對(duì)象鎖的狀態(tài)冤馏,0-未鎖住 1-已鎖住
mutexWoken // 2 表示是否被喚醒 0010 含義:用倒數(shù)第二位表示當(dāng)前對(duì)象是否被喚醒 0- 未喚醒 1-喚醒 【注意: 未被喚醒并不是指 休眠,而是指為了讓所能被設(shè)置 被喚醒的一個(gè)初始值】
mutexStarving // 4 表示是否饑餓 0100 含義:用倒數(shù)第三位表示當(dāng)前對(duì)象是否為饑餓模式寄啼,0為正常模式逮光,1為饑餓模式。
mutexWaiterShift = iota // 3 表示 從倒數(shù)第四位往前的bit位表示在排隊(duì)等待的goroutine數(shù)目(共對(duì)于 32位中占用 29 位)
//
/** 互斥量可分為兩種操作模式:正常和饑餓墩划。
【正常模式】涕刚,等待的goroutines按照FIFO(先進(jìn)先出)順序排隊(duì),但是goroutine被喚醒之后并不能立即得到mutex鎖乙帮,它需要與新到達(dá)的goroutine爭(zhēng)奪mutex鎖杜漠。
因?yàn)樾碌竭_(dá)的goroutine已經(jīng)在CPU上運(yùn)行了,所以被喚醒的goroutine很大概率是爭(zhēng)奪mutex鎖是失敗的察净。出現(xiàn)這樣的情況時(shí)候驾茴,被喚醒的goroutine需要排隊(duì)在隊(duì)列的前面。
如果被喚醒的goroutine有超過1ms沒有獲取到mutex鎖氢卡,那么它就會(huì)變?yōu)轲囸I模式锈至。
在饑餓模式中,mutex鎖直接從解鎖的goroutine交給隊(duì)列前面的goroutine译秦。新達(dá)到的goroutine也不會(huì)去爭(zhēng)奪mutex鎖(即使沒有鎖峡捡,也不能去自旋)击碗,而是到等待隊(duì)列尾部排隊(duì)。
【饑餓模式】棋返,鎖的所有權(quán)將從unlock的gorutine直接交給交給等待隊(duì)列中的第一個(gè)延都。新來的goroutine將不會(huì)嘗試去獲得鎖雷猪,即使鎖看起來是unlock狀態(tài), 也不會(huì)去嘗試自旋操作睛竣,而是放在等待隊(duì)列的尾部。如果有一個(gè)等待的goroutine獲取到mutex鎖了求摇,如果它滿足下條件中的任意一個(gè)射沟,mutex將會(huì)切換回去正常模式:
1. 是等待隊(duì)列中的最后一個(gè)goroutine
2. 它的等待時(shí)間不超過1ms。
正常模式:有更好的性能与境,因?yàn)間oroutine可以連續(xù)多次獲得mutex鎖验夯;
饑餓模式:能阻止尾部延遲的現(xiàn)象,對(duì)于預(yù)防隊(duì)列尾部goroutine一致無法獲取mutex鎖的問題摔刁。
*/
starvationThresholdNs = 1e6 // 1ms
)
// 如果鎖已經(jīng)在使用中挥转,則調(diào)用goroutine 直到互斥鎖可用為止。
/**
在此之前我們必須先說下 四個(gè)重要的方法共屈;
【runtime_canSpin】绑谣,【runtime_doSpin】,【runtime_SemacquireMutex】拗引,【runtime_Semrelease】
【runtime_canSpin】: 在 src/runtime/proc.go 中被實(shí)現(xiàn) sync_runtime_canSpin借宵; 表示 比較保守的自旋,
golang中自旋鎖并不會(huì)一直自旋下去矾削,在runtime包中runtime_canSpin方法做了一些限制,
傳遞過來的iter大等于4或者cpu核數(shù)小等于1壤玫,最大邏輯處理器大于1,至少有個(gè)本地的P隊(duì)列哼凯,
并且本地的P隊(duì)列可運(yùn)行G隊(duì)列為空欲间。
【runtime_doSpin】: 在src/runtime/proc.go 中被實(shí)現(xiàn) sync_runtime_doSpin;表示 會(huì)調(diào)用procyield函數(shù)断部,
該函數(shù)也是匯編語言實(shí)現(xiàn)猎贴。函數(shù)內(nèi)部循環(huán)調(diào)用PAUSE指令。PAUSE指令什么都不做家坎,
但是會(huì)消耗CPU時(shí)間嘱能,在執(zhí)行PAUSE指令時(shí),CPU不會(huì)對(duì)它做不必要的優(yōu)化虱疏。
【runtime_SemacquireMutex】:在 src/runtime/sema.go 中被實(shí)現(xiàn) sync_runtime_SemacquireMutex惹骂;表示通過信號(hào)量 阻塞當(dāng)前協(xié)程
【runtime_Semrelease】: 在src/runtime/sema.go 中被實(shí)現(xiàn) sync_runtime_Semrelease
*/
func (m *Mutex) Lock() {
// 如果m.state為 0,說明當(dāng)前的對(duì)象還沒有被鎖住做瞪,進(jìn)行原子性賦值操作設(shè)置為mutexLocked狀態(tài)对粪,CompareAnSwapInt32返回true
// 否則說明對(duì)象已被其他goroutine鎖住右冻,不會(huì)進(jìn)行原子賦值操作設(shè)置,CopareAndSwapInt32返回false
/**
如果mutext的state沒有被鎖著拭,也沒有等待/喚醒的goroutine, 鎖處于正常狀態(tài)纱扭,那么獲得鎖,返回.
比如鎖第一次被goroutine請(qǐng)求時(shí)儡遮,就是這種狀態(tài)乳蛾。或者鎖處于空閑的時(shí)候鄙币,也是這種狀態(tài)
*/
if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {
if race.Enabled {
race.Acquire(unsafe.Pointer(m))
}
return
}
/** 在 鎖定沒有成功的時(shí)候肃叶,才會(huì)往下面走 */
// 首先判斷是否已經(jīng)加鎖并處于 正常模式,
// 將原先鎖的state & (1 和 4 | 的結(jié)果十嘿,目的就是為了檢驗(yàn) state 是處于 1 還是 4 狀態(tài)因惭, 還是兩者都是.
// 如果與1相等,則說明此時(shí)處于 正常模式并且已經(jīng)加鎖绩衷,而后判斷當(dāng)前協(xié)程是否可以自旋蹦魔。
// 如果可以自旋,則通過右移三位判斷是否還有協(xié)程正在等待這個(gè)鎖咳燕,
// 如果有勿决,并通過 低2位 判斷是否該所處于被喚醒狀態(tài),
// 如果并沒有迟郎,則將其狀態(tài)量設(shè)為被喚醒的狀態(tài)剥险,之后進(jìn)行自旋,直到該協(xié)程自旋數(shù)量達(dá)到上限宪肖,
// 或者當(dāng)前鎖被解鎖表制,
// 或者當(dāng)前鎖已經(jīng)處于 饑餓模式
// 標(biāo)記本goroutine的等待時(shí)間
// 開始等待時(shí)間戳
var waitStartTime int64
// 本goroutine是否已經(jīng)處于饑餓狀態(tài)
// 饑餓模式標(biāo)識(shí) true: 饑餓 false: 未饑餓
starving := false
// 本goroutine是否已喚醒
// 被喚醒標(biāo)識(shí) true: 被喚醒 flase: 未被喚醒
awoke := false
// 自旋次數(shù)
iter := 0
// 保存當(dāng)前對(duì)象鎖狀態(tài),做對(duì)比用
old := m.state
// for 來實(shí)現(xiàn) CAS(Compare and Swap) 非阻塞同步算法 (對(duì)比交換)
for {
// 不要在饑餓模式下自旋控乾,將鎖的控制權(quán)交給阻塞任務(wù)么介,否則無論如何 當(dāng)前goroutine都無法獲得互斥鎖。
// 相當(dāng)于xxxx...x0xx & 0101 = 01蜕衡,當(dāng)前對(duì)象鎖被使用
// old & (是否鎖定|是否饑餓) == 是否鎖定
// runtime_canSpin() 表示 是否可以自旋壤短。runtime_canSpin返回true,可以自旋慨仿。即: 判斷當(dāng)前goroutine是否可以進(jìn)入自旋鎖
/**
第一個(gè)條件:是state已被鎖久脯,但是不是饑餓狀態(tài)。如果時(shí)饑餓狀態(tài)镰吆,自旋時(shí)沒有用的帘撰,鎖的擁有權(quán)直接交給了等待隊(duì)列的第一個(gè)。
第二個(gè)條件:是還可以自旋万皿,多核摧找、壓力不大并且在一定次數(shù)內(nèi)可以自旋核行, 具體的條件可以參考`sync_runtime_canSpin`的實(shí)現(xiàn)的止。
如果滿足這兩個(gè)條件线欲,不斷自旋來等待鎖被釋放、或者進(jìn)入饑餓狀態(tài)不脯、或者不能再自旋综苔。
*/
if old&(mutexLocked|mutexStarving) == mutexLocked && runtime_canSpin(iter) {
// 主動(dòng)旋轉(zhuǎn)是有意義的惩系。試著設(shè)置 mutexWoken (鎖喚醒)標(biāo)志,告知解鎖休里,不喚醒其他阻塞的goroutines蛆挫。
// old&mutexWoken == 0 再次確定是否被喚醒: xxxx...xx0x & 0010 = 0
// old>>mutexWaiterShift != 0 查看是否有g(shù)oroution在排隊(duì)
// tomic.CompareAndSwapInt32(&m.state, old, old|mutexWoken) 將對(duì)象鎖在老狀態(tài)上追加喚醒狀態(tài):xxxx...xx0x | 0010 = xxxx...xx1x
// 如果當(dāng)前標(biāo)識(shí)位 awoke為 未被喚醒 && (old 也為 未被喚醒) && 有正在等待的 goroutine && 則修改 old 為 被喚醒
// 且修改標(biāo)識(shí)位 awoke 為 true 被喚醒
/**
自旋的過程中如果發(fā)現(xiàn)state還沒有設(shè)置woken標(biāo)識(shí)赃承,則設(shè)置它的woken標(biāo)識(shí)妙黍, 并標(biāo)記自己為被喚醒。
*/
if !awoke && old&mutexWoken == 0 && old>>mutexWaiterShift != 0 && atomic.CompareAndSwapInt32(&m.state, old, old|mutexWoken) {
// 更改標(biāo)識(shí)位為 喚醒true
awoke = true
}
// 否則: 進(jìn)入自旋
// 進(jìn)入自旋鎖后當(dāng)前goroutine并不掛起瞧剖,仍然在占用cpu資源拭嫁,所以重試一定次數(shù)后,不會(huì)再進(jìn)入自旋鎖邏輯
runtime_doSpin()
// 累加自旋次數(shù)
iter++
// 更新中轉(zhuǎn)變量
// 保存mutex對(duì)象即將被設(shè)置成的狀態(tài)
old = m.state
continue
}
// 以下代碼是不使用**自旋**的情況
/**
到了這一步抓于, state的狀態(tài)可能是:
1. 鎖還沒有被釋放做粤,鎖處于正常狀態(tài)
2. 鎖還沒有被釋放, 鎖處于饑餓狀態(tài)
3. 鎖已經(jīng)被釋放捉撮, 鎖處于正常狀態(tài)
4. 鎖已經(jīng)被釋放怕品, 鎖處于饑餓狀態(tài)
并且本gorutine的 awoke可能是true, 也可能是false (其它goutine已經(jīng)設(shè)置了state的woken標(biāo)識(shí))
new 復(fù)制 state的當(dāng)前狀態(tài), 用來設(shè)置新的狀態(tài)
old 是鎖當(dāng)前的狀態(tài)
*/
new := old
/** 下面的幾個(gè) if 分別是并列語句巾遭,來判斷如給 設(shè)置 state 的new 狀態(tài) */
/**
如果old state狀態(tài)不是饑餓狀態(tài), new state 設(shè)置鎖肉康, 嘗試通過CAS獲取鎖,
如果old state狀態(tài)是饑餓狀態(tài), 則不設(shè)置new state的鎖,因?yàn)轲囸I狀態(tài)下鎖直接轉(zhuǎn)給等待隊(duì)列的第一個(gè).
*/
// 不要試圖獲得饑餓goroutine的互斥鎖灼舍,新來的goroutines必須排隊(duì)吼和。
// 對(duì)象鎖饑餓位被改變 為 1 ,說明處于饑餓模式
// xxxx...x0xx & 0100 = 0xxxx...x0xx
/**【一】如果是正常狀態(tài) (如果是正常骑素,則可以競(jìng)爭(zhēng)到鎖) */
if old&mutexStarving == 0 {
// xxxx...x0xx | 0001 = xxxx...x0x1炫乓,將標(biāo)識(shí)對(duì)象鎖被鎖住
new |= mutexLocked
}
/** 【二】處于饑餓且鎖被占用 狀態(tài)下 */
// xxxx...x1x1 & (0001 | 0100) => xxxx...x1x1 & 0101 != 0;當(dāng)前mutex處于饑餓模式并且鎖已被占用,新加入進(jìn)來的goroutine放到隊(duì)列后面献丑,所以 等待者數(shù)目 +1
if old&(mutexLocked|mutexStarving) != 0 {
// 更新阻塞goroutine的數(shù)量,表示mutex的等待goroutine數(shù)目加1
// 首先末捣,如果此時(shí)還是由于別的協(xié)程的占用 無法獲得鎖 或者 處于 饑餓模式,都在其state加8表示有新的協(xié)程正在處于等待狀態(tài)
new += 1 << mutexWaiterShift
}
/**
如果之前由于自旋而將該鎖喚醒创橄,那么此時(shí)將其低二位的狀態(tài)量重置為0 (即 未被喚醒)箩做。
之后判斷starving是否為true,如果為true說明在上一次的循環(huán)中筐摘,
鎖需要被定義為 饑餓模式卒茬,那么在這里就將相應(yīng)的狀態(tài)量低3位設(shè)置為1表示進(jìn)入饑餓模式
*/
/***
【三】
如果當(dāng)前goroutine已經(jīng)處于饑餓狀態(tài) (表示當(dāng)前 goroutine 的饑餓標(biāo)識(shí)位 starving)船老, 并且old state的已被加鎖,
將new state的狀態(tài)標(biāo)記為饑餓狀態(tài), 將鎖轉(zhuǎn)變?yōu)轲囸I狀態(tài).
*/
// 當(dāng)前的goroutine將互斥鎖轉(zhuǎn)換為饑餓模式。但是圃酵,如果互斥鎖當(dāng)前沒有解鎖柳畔,就不要打開開關(guān),設(shè)置mutex狀態(tài)為饑餓模式。Unlock預(yù)期有饑餓的goroutine
// old&mutexLocked != 0 xxxx...xxx1 & 0001 != 0郭赐;鎖已經(jīng)被占用
// 如果 饑餓且已被鎖定
if starving && old&mutexLocked != 0 {
// 【追加】饑餓狀態(tài)
new |= mutexStarving
}
/**
【四】
如果本goroutine已經(jīng)設(shè)置為喚醒狀態(tài), 需要清除new state的喚醒標(biāo)記, 因?yàn)楸緂oroutine要么獲得了鎖薪韩,要么進(jìn)入休眠,
總之state的新狀態(tài)不再是woken狀態(tài).
*/
// 如果 goroutine已經(jīng)被喚醒捌锭,因此需要在兩種情況下重設(shè)標(biāo)志
if awoke {
// xxxx...xx0x & 0010 == 0,如果喚醒標(biāo)志為與awoke的值不相協(xié)調(diào)就panic
// 即 state 為 未被喚醒
if new&mutexWoken == 0 {
panic("sync: inconsistent mutex state")
}
// new & (^mutexWoken) => xxxx...xxxx & (^0010) => xxxx...xxxx & 1101 = xxxx...xx0x
// 設(shè)置喚醒狀態(tài)位0,被 未喚醒【只是為了俘陷, 下次被可被設(shè)置為i被喚醒的 初識(shí)化標(biāo)識(shí),而不是指休眠】
new &^= mutexWoken
}
/**
之后嘗試通過cas將 new 的state狀態(tài)量賦值給state观谦,
如果失敗拉盾,則重新獲得其 state在下一步循環(huán)重新重復(fù)上述的操作。
如果成功豁状,首先判斷已經(jīng)阻塞時(shí)間 (通過 標(biāo)記本goroutine的等待時(shí)間 waitStartTime )捉偏,如果為零,則從現(xiàn)在開始記錄
*/
// 將新的狀態(tài)賦值給 state
// 注意new的鎖標(biāo)記不一定是true, 也可能只是標(biāo)記一下鎖的state是饑餓狀態(tài)
if atomic.CompareAndSwapInt32(&m.state, old, new) {
/**
如果old state的狀態(tài)是未被鎖狀態(tài)泻红,并且鎖不處于饑餓狀態(tài),
那么當(dāng)前goroutine已經(jīng)獲取了鎖的擁有權(quán)夭禽,返回
*/
// xxxx...x0x0 & 0101 = 0,表示可以獲取對(duì)象鎖 (即 還是判斷之前的狀態(tài)谊路,鎖不是饑餓 也不是被被鎖定讹躯, 所已經(jīng)可用了)
if old&(mutexLocked|mutexStarving) == 0 {
break // 結(jié)束cas
}
// 以下的操作都是為了判斷是否從【饑餓模式】中恢復(fù)為【正常模式】
// 判斷處于FIFO還是LIFO模式
// 如果等待時(shí)間不為0 那么就是 LIFO
// 在正常模式下,等待的goroutines按照FIFO(先進(jìn)先出)順序排隊(duì)
/**
設(shè)置/計(jì)算本goroutine的等待時(shí)間
*/
queueLifo := waitStartTime != 0
if waitStartTime == 0 {
// 更新等待時(shí)間
waitStartTime = runtime_nanotime()
}
// 通過runtime_SemacquireMutex()通過信號(hào)量將當(dāng)前協(xié)程阻塞
// 函數(shù) runtime_SemacquireMutex 定義在 sema.go
/**
既然未能獲取到鎖缠劝, 那么就使用 [sleep原語] 阻塞本goroutine
如果是新來的goroutine,queueLifo=false, 加入到等待隊(duì)列的尾部潮梯,耐心等待
如果是喚醒的goroutine, queueLifo=true, 加入到等待隊(duì)列的頭部
*/
runtime_SemacquireMutex(&m.sema, queueLifo)
// 當(dāng)之前調(diào)用 runtime_SemacquireMutex 方法將當(dāng)前新進(jìn)來爭(zhēng)奪鎖的協(xié)程掛起后,如果協(xié)程被喚醒剩彬,那么就會(huì)繼續(xù)下面的流程
// 如果當(dāng)前 饑餓狀態(tài)標(biāo)識(shí)為 饑餓 || 當(dāng)前時(shí)間 - 開始等待時(shí)間 > 1 ms 則 都切換為饑餓狀態(tài)標(biāo)識(shí)
/**
使用 [sleep原語] 之后酷麦,此goroutine被喚醒
計(jì)算當(dāng)前goroutine是否已經(jīng)處于饑餓狀態(tài).
*/
starving = starving || runtime_nanotime()-waitStartTime > starvationThresholdNs
// 刷新下 中轉(zhuǎn)變量
/** 得到當(dāng)前的鎖狀態(tài) */
old = m.state
/**
如果當(dāng)前的state已經(jīng)是饑餓狀態(tài)
那么鎖應(yīng)該處于Unlock狀態(tài),那么應(yīng)該是鎖被直接交給了本goroutine
*/
// xxxx...x1xx & 0100 != 0 處于 饑餓狀態(tài)
if old&mutexStarving != 0 {
/**
如果當(dāng)前的state已被鎖喉恋,或者已標(biāo)記為喚醒沃饶, 或者等待的隊(duì)列中為空,
那么state是一個(gè)非法狀態(tài)
*/
// xxxx...xx11 & 0011 != 0 又可能是被鎖定,又可能是被喚醒 或者 沒有等待的goroutine
if old&(mutexLocked|mutexWoken) != 0 || old>>mutexWaiterShift == 0 {
panic("sync: inconsistent mutex state")
}
// delta 表示當(dāng)前狀態(tài)下的等待數(shù)
// 否則下一次的循環(huán)中將該鎖這是為 饑餓模式轻黑。
// 如果已經(jīng)是這個(gè)模式糊肤,那么就會(huì)將 狀態(tài)量的等待數(shù) 減1
/**
當(dāng)前goroutine用來設(shè)置鎖,并將等待的goroutine數(shù)減1.
lock狀態(tài) -一個(gè)gorotine數(shù)氓鄙,表示 狀態(tài) delta == (lock + (減去一個(gè)等待goroutine數(shù)))
*/
delta := int32(mutexLocked - 1<<mutexWaiterShift)
// 并判斷當(dāng)前如果已經(jīng)沒有等待的協(xié)程馆揉,就沒有必要繼續(xù)維持 饑餓模式,同時(shí)也沒必要繼續(xù)執(zhí)行該循環(huán)(當(dāng)前只有一個(gè)協(xié)程在占用鎖)
/**
如果本goroutine并不處于饑餓狀態(tài)抖拦,或者它是最后一個(gè)等待者升酣,
那么我們需要把鎖的state狀態(tài)設(shè)置為正常模式.
*/
if !starving || old>>mutexWaiterShift == 1 {
// 退出饑餓模式舷暮。
// 在這里做到并考慮等待時(shí)間至關(guān)重要。
// 饑餓模式是如此低效噩茄,一旦將互斥鎖切換到饑餓模式下面,兩個(gè)goroutine就可以無限鎖定。
delta -= mutexStarving
}
// 設(shè)置新state, 因?yàn)橐呀?jīng)獲得了鎖绩聘,退出沥割、返回
atomic.AddInt32(&m.state, delta)
break
}
// 修改為 本goroutine 是否被喚醒標(biāo)識(shí)位
/**
如果當(dāng)前的鎖是正常模式,本goroutine被喚醒凿菩,自旋次數(shù)清零机杜,從for循環(huán)開始處重新開始
*/
awoke = true
// 自旋計(jì)數(shù) 初始化
iter = 0
} else {
// 如果CAS不成功,重新獲取鎖的state, 從for循環(huán)開始處重新開始 繼續(xù)上述動(dòng)作
old = m.state
}
}
if race.Enabled {
race.Acquire(unsafe.Pointer(m))
}
}
// 解鎖一個(gè)未被鎖定的互斥鎖時(shí)衅谷,是會(huì)報(bào)錯(cuò)
// 鎖定的互斥鎖與特定的goroutine無關(guān)椒拗。
// 允許一個(gè)goroutine鎖定Mutex然后
// 安排另一個(gè)goroutine解鎖它。
func (m *Mutex) Unlock() {
if race.Enabled {
_ = m.state
race.Release(unsafe.Pointer(m))
}
/** 如果state不是處于鎖的狀態(tài), 那么就是Unlock根本沒有加鎖的mutex, panic */
// state -1 標(biāo)識(shí)解鎖 (移除鎖定標(biāo)記)
new := atomic.AddInt32(&m.state, -mutexLocked)
/**
釋放了鎖会喝,還得需要通知其它等待者
被通知的 goroutine 會(huì)去做下面的事情
鎖如果處于饑餓狀態(tài)陡叠,直接交給等待隊(duì)列的第一個(gè), 喚醒它,讓它去獲取鎖
鎖如果處于正常狀態(tài)肢执,則需要喚醒對(duì)頭的 goroutine 讓它和新來的goroutine去競(jìng)爭(zhēng)鎖,當(dāng)然極大幾率為失敗译红,
這時(shí)候 被喚醒的goroutine需要排隊(duì)在隊(duì)列的前面 (然后自旋)预茄。如果被喚醒的goroutine有超過1ms沒有獲取到mutex鎖,那么它就會(huì)變?yōu)轲囸I模式
*/
// 再次校驗(yàn)下 標(biāo)識(shí)侦厚,new state如果是正常狀態(tài), 驗(yàn)證鎖狀態(tài)是否符合
if (new+mutexLocked)&mutexLocked == 0 {
panic("sync: unlock of unlocked mutex")
}
// xxxx...x0xx & 0100 = 0 ;判斷是否處于正常模式
if new&mutexStarving == 0 {
// 記錄緩存值
old := new
for {
// 如果沒有等待的goroutine或goroutine不處于空閑耻陕,則無需喚醒任何人
// 在饑餓模式下,鎖的所有權(quán)直接從解鎖goroutine交給下一個(gè) 正在等待的goroutine (等待隊(duì)列中的第一個(gè))刨沦。
// 注意: old&(mutexLocked|mutexWoken|mutexStarving) 中诗宣,因?yàn)樵谧钌厦嬉呀?jīng) -mutexLocked 并且進(jìn)入了 if new&mutexStarving == 0
// 說明目前 只有在還有g(shù)oroutine 或者 被喚醒的情況下才會(huì) old&(mutexLocked|mutexWoken|mutexStarving) != 0
// 即:當(dāng)休眠隊(duì)列內(nèi)的等待計(jì)數(shù)為 0 或者 是正常但是 處于被喚醒或者被鎖定狀態(tài),退出
// old&(mutexLocked|mutexWoken|mutexStarving) != 0 xxxx...x0xx & (0001 | 0010 | 0100) => xxxx...x0xx & 0111 != 0
/**
如果沒有等待的goroutine, 或者鎖不處于空閑的狀態(tài)想诅,直接返回.
*/
if old>>mutexWaiterShift == 0 || old&(mutexLocked|mutexWoken|mutexStarving) != 0 {
return
}
// 減少等待goroutine個(gè)數(shù)召庞,并添加 喚醒標(biāo)識(shí)
new = (old - 1<<mutexWaiterShift) | mutexWoken
/** 設(shè)置新的state, 這里通過 信號(hào)量 去喚醒一個(gè)阻塞的goroutine去獲取鎖. */
if atomic.CompareAndSwapInt32(&m.state, old, new) {
// 釋放鎖,發(fā)送釋放信號(hào) (解除 阻塞信號(hào)量)
runtime_Semrelease(&m.sema, false)
return
}
// 賦值給中轉(zhuǎn)變量,然后啟動(dòng)下一輪
old = m.state
}
} else {
/**
饑餓模式下:
直接將鎖的擁有權(quán)傳給等待隊(duì)列中的第一個(gè).
注意:
此時(shí)state的mutexLocked還沒有加鎖来破,喚醒的goroutine會(huì)設(shè)置它篮灼。
在此期間,如果有新的goroutine來請(qǐng)求鎖徘禁, 因?yàn)閙utex處于饑餓狀態(tài)诅诱, mutex還是被認(rèn)為處于鎖狀態(tài),
新來的goroutine不會(huì)把鎖搶過去.
*/
runtime_Semrelease(&m.sema, true)
}
}