鎖飢餓是指由於大量線程都同時進行獲取鎖,某些線程可能在鎖的CAS過程當中一直失敗,從而長時間獲取不到鎖git
在大多數編程語言中針對實現基於CAS的鎖的時候,一般都會採用一個32位的整數來進行鎖狀態的存儲github
在go的mutex中核心成員變量只有兩個state和sema,其經過state來進行鎖的計數,而經過sema來實現排隊編程
type Mutex struct {
state int32
sema uint32
}
複製代碼
鎖模式主要分爲兩種多線程
描述 | 公平性 | |
---|---|---|
正常模式 | 正常模式下全部的goroutine按照FIFO的順序進行鎖獲取,被喚醒的goroutine和新請求鎖的goroutine同時進行鎖獲取,一般新請求鎖的goroutine更容易獲取鎖 | 否 |
飢餓模式 | 飢餓模式全部嘗試獲取鎖的goroutine進行等待排隊,新請求鎖的goroutine不會進行鎖獲取,而是加入隊列尾部等待獲取鎖 | 是 |
上面能夠看到其實在正常模式下,其實鎖的性能是最高的若是多個goroutine進行鎖獲取後立馬進行釋放則能夠避免多個線程的排隊消耗 同理在切換到飢餓模式後,在進行鎖獲取的時候,若是知足必定的條件也會切換回正常模式,從而保證鎖的高性能併發
mutexLocked = 1 << iota // mutex is locked
mutexWoken
mutexStarving
複製代碼
mutex中經過低3位存儲了當前mutex的三種狀態,剩下的29位所有用來存儲嘗試正在等待獲取鎖的goroutine的數量編程語言
mutexWaiterShift = iota // 3
複製代碼
若是當前沒有goroutine加鎖,則而且直接進行CAS成功,則直接獲取鎖成功ide
// Fast path: grab unlocked mutex.
if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {
if race.Enabled {
race.Acquire(unsafe.Pointer(m))
}
return
}
複製代碼
// 注意這裏其實包含兩個信息一個是若是當前已是鎖定狀態,而後容許自旋iter主要是計數次數實際上只容許自旋4次
// 其實就是在自旋而後等待別人釋放鎖,若是有人釋放鎖,則會馬上進行下面的嘗試獲取鎖的邏輯
if old&(mutexLocked|mutexStarving) == mutexLocked && runtime_canSpin(iter) {
// !awoke 若是當前線程不處於喚醒狀態
// old&mutexWoken == 0若是當前沒有其餘正在喚醒的節點,就將當前節點處於喚醒的狀態
// old>>mutexWaiterShift != 0 :右移3位,若是不位0,則代表當前有正在等待的goroutine
// atomic.CompareAndSwapInt32(&m.state, old, old|mutexWoken)設置當前狀態爲喚醒狀態
if !awoke && old&mutexWoken == 0 && old>>mutexWaiterShift != 0 &&
atomic.CompareAndSwapInt32(&m.state, old, old|mutexWoken) {
awoke = true
}
// 嘗試自旋,
runtime_doSpin()
// 自旋計數
iter++
// 重新獲取狀態
old = m.state
continue
}
複製代碼
流程走到這裏會有兩種可能: 1.鎖狀態當前已經不是鎖定狀態 2.自旋超過指定的次數,再也不容許自旋了源碼分析
new := old
if old&mutexStarving == 0 {
// 若是當前不是飢餓模式,則這裏其實就能夠嘗試進行鎖的獲取了|=其實就是將鎖的那個bit位設爲1表示鎖定狀態
new |= mutexLocked
}
if old&(mutexLocked|mutexStarving) != 0 {
// 若是當前被鎖定或者處於飢餓模式,則增等待一個等待計數
new += 1 << mutexWaiterShift
}
if starving && old&mutexLocked != 0 {
// 若是當前已經處於飢餓狀態,而且當前鎖仍是被佔用,則嘗試進行飢餓模式的切換
new |= mutexStarving
}
if awoke {
if new&mutexWoken == 0 {
throw("sync: inconsistent mutex state")
}
// awoke爲true則代表當前線程在上面自旋的時候,修改mutexWoken狀態成功
// 清除喚醒標誌位
// 爲何要清除標誌位呢?
// 其實是由於後續流程頗有可能當前線程會被掛起,就須要等待其餘釋放鎖的goroutine來喚醒
// 但若是unlock的時候發現mutexWoken的位置不是0,則就不會去喚醒,則該線程就沒法再醒來加鎖
new &^= mutexWoken
}
複製代碼
再加鎖的時候實際上只會有一個goroutine加鎖CAS成功,而其餘線程則須要從新獲取狀態,進行上面的自旋與喚醒狀態的從新計算,從而再次CAS性能
if atomic.CompareAndSwapInt32(&m.state, old, new) {
if old&(mutexLocked|mutexStarving) == 0 {
// 若是原來的狀態等於0則代表當前已經釋放了鎖而且也不處於飢餓模式下
// 實際的二進制位多是這樣的 1111000, 後面三位全是0,只有記錄等待goroutine的計數器可能會不爲0
// 那就代表其實
break // locked the mutex with CAS
}
// 排隊邏輯,若是發現waitStatrTime不爲0,則代表當前線程以前已經再排隊來,後面可能由於
// unlock被喚醒,可是本次依舊沒獲取到鎖,因此就將它移動到等待隊列的頭部
queueLifo := waitStartTime != 0
if waitStartTime == 0 {
waitStartTime = runtime_nanotime()
}
// 這裏就會進行排隊等待其餘節點進行喚醒
runtime_SemacquireMutex(&m.sema, queueLifo)
// 若是等待超過指定時間,則切換爲飢餓模式 starving=true
// 若是一個線程以前不是飢餓狀態,而且也沒超過starvationThresholdNs,則starving爲false
// 就會觸發下面的狀態切換
starving = starving || runtime_nanotime()-waitStartTime > starvationThresholdNs
// 從新獲取狀態
old = m.state
if old&mutexStarving != 0 {
// 若是發現當前已是飢餓模式,注意飢餓模式喚醒的是第一個goroutine
// 當前全部的goroutine都在排隊等待
// 一致性檢查,
if old&(mutexLocked|mutexWoken) != 0 || old>>mutexWaiterShift == 0 {
throw("sync: inconsistent mutex state")
}
// 獲取當前的模式
delta := int32(mutexLocked - 1<<mutexWaiterShift)
if !starving || old>>mutexWaiterShift == 1 {
// 若是當前goroutine不是飢餓狀態,就從飢餓模式切換會正常模式
// 就從mutexStarving狀態切換出去
delta -= mutexStarving
}
// 最後進行cas操做
atomic.AddInt32(&m.state, delta)
break
}
// 重置計數
awoke = true
iter = 0
} else {
old = m.state
}
複製代碼
func (m *Mutex) Unlock() {
if race.Enabled {
_ = m.state
race.Release(unsafe.Pointer(m))
}
// 直接進行cas操做
new := atomic.AddInt32(&m.state, -mutexLocked)
if (new+mutexLocked)&mutexLocked == 0 {
throw("sync: unlock of unlocked mutex")
}
if new&mutexStarving == 0 {
// 若是釋放鎖而且不是飢餓模式
old := new
for {
if old>>mutexWaiterShift == 0 || old&(mutexLocked|mutexWoken|mutexStarving) != 0 {
// 若是已經有等待者而且已經被喚醒,就直接返回
return
}
// 減去一個等待計數,而後將當前模式切換成mutexWoken
new = (old - 1<<mutexWaiterShift) | mutexWoken
if atomic.CompareAndSwapInt32(&m.state, old, new) {
// 喚醒一個goroutine
runtime_Semrelease(&m.sema, false)
return
}
old = m.state
}
} else {
// 喚醒等待的線程
runtime_Semrelease(&m.sema, true)
}
}
複製代碼
關注公告號閱讀更多源碼分析文章ui
更多文章關注 www.sreguide.com
本篇文章由一文多發平臺ArtiPub自動發佈