真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

如何理解Go里面的互斥鎖mutex

如何理解Go里面的互斥鎖mutex,相信很多沒有經(jīng)驗的人對此束手無策,為此本文總結(jié)了問題出現(xiàn)的原因和解決方法,通過這篇文章希望你能解決這個問題。

創(chuàng)新互聯(lián)是工信部頒發(fā)資質(zhì)IDC服務(wù)器商,為用戶提供優(yōu)質(zhì)的成都服務(wù)器托管服務(wù)

1. 鎖的基礎(chǔ)概念

1.1 CAS與輪詢

1.1.1 cas實現(xiàn)鎖

如何理解Go里面的互斥鎖mutex 在鎖的實現(xiàn)中現(xiàn)在越來越多的采用CAS來進(jìn)行,通過利用處理器的CAS指令來實現(xiàn)對給定變量的值交換來進(jìn)行鎖的獲取

1.1.2 輪詢鎖

如何理解Go里面的互斥鎖mutex 在多線程并發(fā)的情況下很有可能會有線程CAS失敗,通常就會配合for循環(huán)采用輪詢的方式去嘗試重新獲取鎖

1.2 鎖的公平性

如何理解Go里面的互斥鎖mutex 鎖從公平性上通常會分為公平鎖和非公平鎖,主要取決于在鎖獲取的過程中,先進(jìn)行鎖獲取的線程是否比后續(xù)的線程更先獲得鎖,如果是則就是公平鎖:多個線程按照獲取鎖的順序依次獲得鎖,否則就是非公平性

1.3 饑餓與排隊

1.3.1 鎖饑餓

鎖饑餓是指因為大量線程都同時進(jìn)行獲取鎖,某些線程可能在鎖的CAS過程中一直失敗,從而長時間獲取不到鎖

1.3.2 排隊機(jī)制

如何理解Go里面的互斥鎖mutex 上面提到了CAS和輪詢鎖進(jìn)行鎖獲取的方式,可以發(fā)現(xiàn)如果已經(jīng)有線程獲取了鎖,但是在當(dāng)前線程在多次輪詢獲取鎖失敗的時候,就沒有必要再繼續(xù)進(jìn)行反復(fù)嘗試?yán)速M系統(tǒng)資源,通常就會采用一種排隊機(jī)制,來進(jìn)行排隊等待

1.4 位計數(shù)

在大多數(shù)編程語言中針對實現(xiàn)基于CAS的鎖的時候,通常都會采用一個32位的整數(shù)來進(jìn)行鎖狀態(tài)的存儲

2. mutex實現(xiàn)

2.1 成員變量與模式

2.1.1 成員變量

在go的mutex中核心成員變量只有兩個state和sema,其通過state來進(jìn)行鎖的計數(shù),而通過sema來實現(xiàn)排隊

type Mutex struct {
	state int32
	sema  uint32
}

2.1.2 鎖模式

鎖模式主要分為兩種


描述公平性
正常模式正常模式下所有的goroutine按照FIFO的順序進(jìn)行鎖獲取,被喚醒的goroutine和新請求鎖的goroutine同時進(jìn)行鎖獲取,通常新請求鎖的goroutine更容易獲取鎖
饑餓模式饑餓模式所有嘗試獲取鎖的goroutine進(jìn)行等待排隊,新請求鎖的goroutine不會進(jìn)行鎖獲取,而是加入隊列尾部等待獲取鎖

上面可以看到其實在正常模式下,其實鎖的性能是最高的如果多個goroutine進(jìn)行鎖獲取后立馬進(jìn)行釋放則可以避免多個線程的排隊消耗 同理在切換到饑餓模式后,在進(jìn)行鎖獲取的時候,如果滿足一定的條件也會切換回正常模式,從而保證鎖的高性能

2.2 鎖計數(shù)

2.2.1 鎖狀態(tài)

如何理解Go里面的互斥鎖mutex 在mutex中鎖有三個標(biāo)志位,其中其二進(jìn)制位分別位001(mutexLocked)、010(mutexWoken)、100(mutexStarving), 注意這三者并不是互斥的關(guān)系,比如一個鎖的狀態(tài)可能是鎖定的饑餓模式并且已經(jīng)被喚醒

	mutexLocked = 1 << iota // mutex is locked
	mutexWoken
	mutexStarving

2.2.2 等待計數(shù)

如何理解Go里面的互斥鎖mutex

mutex中通過低3位存儲了當(dāng)前mutex的三種狀態(tài),剩下的29位全部用來存儲嘗試正在等待獲取鎖的goroutine的數(shù)量

	mutexWaiterShift = iota // 3

2.3喚醒機(jī)制

2.3.1 喚醒標(biāo)志

如何理解Go里面的互斥鎖mutex 喚醒標(biāo)志其實就是上面說的第二位,喚醒標(biāo)志主要用于標(biāo)識當(dāng)前嘗試獲取goroutine是否有正在處于喚醒狀態(tài)的,記得上面公平模式下,當(dāng)前正在cpu上運行的goroutine可能會先獲取到鎖

2.3.2 喚醒流程

如何理解Go里面的互斥鎖mutex 當(dāng)釋放鎖的時候,如果當(dāng)前有g(shù)oroutine正在喚醒狀態(tài),則只需要修改鎖狀態(tài)為釋放鎖,則處于woken狀態(tài)的goroutine就可以直接獲取鎖,否則則需要喚醒一個goroutine, 并且等待這個goroutine修改state狀態(tài)為mutexWoken,才退出

2.4 加鎖流程

如何理解Go里面的互斥鎖mutex

2.3.1 快速模式

如果當(dāng)前沒有g(shù)oroutine加鎖,則并且直接進(jìn)行CAS成功,則直接獲取鎖成功

		// Fast path: grab unlocked mutex.
	if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {
		if race.Enabled {
			race.Acquire(unsafe.Pointer(m))
		}
		return
	}

2.3.2 自旋與喚醒

	// 注意這里其實包含兩個信息一個是如果當(dāng)前已經(jīng)是鎖定狀態(tài),然后允許自旋iter主要是計數(shù)次數(shù)實際上只允許自旋4次
	// 其實就是在自旋然后等待別人釋放鎖,如果有人釋放鎖,則會立刻進(jìn)行下面的嘗試獲取鎖的邏輯	
if old&(mutexLocked|mutexStarving) == mutexLocked && runtime_canSpin(iter) {
			// !awoke 如果當(dāng)前線程不處于喚醒狀態(tài)
			// old&mutexWoken == 0如果當(dāng)前沒有其他正在喚醒的節(jié)點,就將當(dāng)前節(jié)點處于喚醒的狀態(tài)
			// old>>mutexWaiterShift != 0 :右移3位,如果不位0,則表明當(dāng)前有正在等待的goroutine
			// atomic.CompareAndSwapInt32(&m.state, old, old|mutexWoken)設(shè)置當(dāng)前狀態(tài)為喚醒狀態(tài)
			if !awoke && old&mutexWoken == 0 && old>>mutexWaiterShift != 0 &&
				atomic.CompareAndSwapInt32(&m.state, old, old|mutexWoken) {
				awoke = true
			}
			// 嘗試自旋,
			runtime_doSpin()
			// 自旋計數(shù)
			iter++
        // 從新獲取狀態(tài)
			old = m.state
			continue
		}

2.3.3 更改鎖狀態(tài)

流程走到這里會有兩種可能: 1.鎖狀態(tài)當(dāng)前已經(jīng)不是鎖定狀態(tài) 2.自旋超過指定的次數(shù),不再允許自旋了

		new := old
		if old&mutexStarving == 0 {
			// 如果當(dāng)前不是饑餓模式,則這里其實就可以嘗試進(jìn)行鎖的獲取了|=其實就是將鎖的那個bit位設(shè)為1表示鎖定狀態(tài)
			new |= mutexLocked
		}
		if old&(mutexLocked|mutexStarving) != 0 {
			// 如果當(dāng)前被鎖定或者處于饑餓模式,則增等待一個等待計數(shù)
			new += 1 << mutexWaiterShift
		}
		if starving && old&mutexLocked != 0 {
			// 如果當(dāng)前已經(jīng)處于饑餓狀態(tài),并且當(dāng)前鎖還是被占用,則嘗試進(jìn)行饑餓模式的切換
			new |= mutexStarving
		}
		if awoke {
			if new&mutexWoken == 0 {
				throw("sync: inconsistent mutex state")
			}
			// awoke為true則表明當(dāng)前線程在上面自旋的時候,修改mutexWoken狀態(tài)成功
			// 清除喚醒標(biāo)志位
            // 為什么要清除標(biāo)志位呢?
            // 實際上是因為后續(xù)流程很有可能當(dāng)前線程會被掛起,就需要等待其他釋放鎖的goroutine來喚醒
            // 但如果unlock的時候發(fā)現(xiàn)mutexWoken的位置不是0,則就不會去喚醒,則該線程就無法再醒來加鎖
			new &^= mutexWoken
		}

2.3.3 加鎖排隊與狀態(tài)轉(zhuǎn)換

再加鎖的時候?qū)嶋H上只會有一個goroutine加鎖CAS成功,而其他線程則需要重新獲取狀態(tài),進(jìn)行上面的自旋與喚醒狀態(tài)的重新計算,從而再次CAS

		if atomic.CompareAndSwapInt32(&m.state, old, new) {
			if old&(mutexLocked|mutexStarving) == 0 {
				// 如果原來的狀態(tài)等于0則表明當(dāng)前已經(jīng)釋放了鎖并且也不處于饑餓模式下
                // 實際的二進(jìn)制位可能是這樣的 1111000, 后面三位全是0,只有記錄等待goroutine的計數(shù)器可能會不為0
                // 那就表明其實
				break // locked the mutex with CAS
			}
			// 排隊邏輯,如果發(fā)現(xiàn)waitStatrTime不為0,則表明當(dāng)前線程之前已經(jīng)再排隊來,后面可能因為
            // unlock被喚醒,但是本次依舊沒獲取到鎖,所以就將它移動到等待隊列的頭部
			queueLifo := waitStartTime != 0
			if waitStartTime == 0 {
				waitStartTime = runtime_nanotime()
			}
            // 這里就會進(jìn)行排隊等待其他節(jié)點進(jìn)行喚醒
			runtime_SemacquireMutex(&m.sema, queueLifo)
			// 如果等待超過指定時間,則切換為饑餓模式 starving=true
            // 如果一個線程之前不是饑餓狀態(tài),并且也沒超過starvationThresholDNS,則starving為false
            // 就會觸發(fā)下面的狀態(tài)切換
			starving = starving || runtime_nanotime()-waitStartTime > starvationThresholdNs
			// 重新獲取狀態(tài)
            old = m.state
			if old&mutexStarving != 0 { 
                // 如果發(fā)現(xiàn)當(dāng)前已經(jīng)是饑餓模式,注意饑餓模式喚醒的是第一個goroutine
                // 當(dāng)前所有的goroutine都在排隊等待
			// 一致性檢查,
				if old&(mutexLocked|mutexWoken) != 0 || old>>mutexWaiterShift == 0 {
					throw("sync: inconsistent mutex state")
				}
				// 獲取當(dāng)前的模式
				delta := int32(mutexLocked - 1<>mutexWaiterShift == 1 {
					// 如果當(dāng)前goroutine不是饑餓狀態(tài),就從饑餓模式切換會正常模式
                    // 就從mutexStarving狀態(tài)切換出去
					delta -= mutexStarving
				}
                // 最后進(jìn)行cas操作
				atomic.AddInt32(&m.state, delta)
				break
			}
            // 重置計數(shù)
			awoke = true
			iter = 0
		} else {
			old = m.state
		}

2.5 釋放鎖邏輯

如何理解Go里面的互斥鎖mutex

2.5.1 釋放鎖代碼

func (m *Mutex) Unlock() {
	if race.Enabled {
		_ = m.state
		race.Release(unsafe.Pointer(m))
	}

	// 直接進(jìn)行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 {
				// 如果已經(jīng)有等待者并且已經(jīng)被喚醒,就直接返回
				return
			}
			// 減去一個等待計數(shù),然后將當(dāng)前模式切換成mutexWoken
			new = (old - 1<

看完上述內(nèi)容,你們掌握如何理解Go里面的互斥鎖mutex的方法了嗎?如果還想學(xué)到更多技能或想了解更多相關(guān)內(nèi)容,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,感謝各位的閱讀!


當(dāng)前題目:如何理解Go里面的互斥鎖mutex
本文URL:http://weahome.cn/article/ipcedo.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部