欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Golang中sync.Mutex的源碼分析

 更新時(shí)間:2023年03月15日 09:42:16   作者:綿羊的微笑  
這篇文章將帶大家從源碼分析一下Golang中sync.Mutex的使用,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)Golang有一定的幫助,需要的可以參考一下

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

type Mutex struct {
	state int32
	sema  uint32
}
  • state 記錄鎖的狀態(tài),轉(zhuǎn)換為二進(jìn)制前29位表示等待鎖的goroutine數(shù)量,后三位從左到右分別表示當(dāng)前g 是否已獲得鎖、是否被喚醒、是否正饑餓
  • sema 充當(dāng)臨界資源,其地址作為這個(gè)鎖在全局的唯一標(biāo)識(shí),所有等待這個(gè)鎖的goroutine都會(huì)在阻塞前把自己的sudog放到這個(gè)鎖的等待隊(duì)列上,然后等待被喚醒,sema的值就是可以被喚醒的goroutine的數(shù)目,只有0和1。

常量

const (
	mutexLocked = 1 << iota // mutex is locked  //值1,轉(zhuǎn)二進(jìn)制后三位為001,表示鎖已被搶
	mutexWoken                                  //值2,轉(zhuǎn)二進(jìn)制后三位為010,告訴即將釋放鎖的g現(xiàn)在已有g(shù)被喚醒
	mutexStarving                               //值4,轉(zhuǎn)二進(jìn)制后三位為100,表示當(dāng)前處在饑餓狀態(tài)
	mutexWaiterShift = iota                     //值3,表示mutex.state右移3位為等待鎖的goroutine數(shù)量
	starvationThresholdNs = 1e6                 //表示mutext切換到饑餓狀態(tài)所需等待時(shí)間的閾值,1ms。
)

Locker接口

type Locker interface {
	Lock()
	Unlock()
}

下面重點(diǎn)看這兩個(gè)方法。

加鎖Lock

Lock()

func (m *Mutex) Lock() {
	// 第一種情況:快上鎖,即此刻無(wú)人來(lái)?yè)屾i
	if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {
		if race.Enabled {  //競(jìng)爭(zhēng)檢測(cè)相關(guān),不用看
			race.Acquire(unsafe.Pointer(m))
		}
		return
	}
	// 第二種情況:慢上鎖,即此刻有競(jìng)爭(zhēng)對(duì)手
	m.lockSlow()
}

CompareAndSwapInt32(addr *int32, old, new int32) (swapped bool){},go的CAS操作,底層通過(guò)調(diào)用cpu指令集提供的CAS指令實(shí)現(xiàn),位置在src/runtime/internal/atomic/atomic_amd64.s/·Cas(SB)。

  • 參數(shù)addr:變量地址
  • 參數(shù)old:舊值
  • 參數(shù)new:新值
  • 原理:如果addr和old相等,則將new賦值給addr,并且返回true,否則返回false

lockSlow()

// 注釋里的第一人稱“我”只當(dāng)前g
func (m *Mutex) lockSlow() {
	var waitStartTime int64	// 等待開(kāi)始的時(shí)間
	starving := false		// 我是否饑餓
	awoke := false			// 我是否被喚醒
	iter := 0				// 我的自旋次數(shù)
	old := m.state			// 這個(gè)鎖此時(shí)此刻所有的信息
	for {
		// 如果:鎖已經(jīng)被搶了 或著 正處在饑餓狀態(tài) 或者 允許我自旋 那么進(jìn)行自旋
		if old&(mutexLocked|mutexStarving) == mutexLocked && runtime_canSpin(iter) {
			// 如果:我沒(méi)有處在喚醒態(tài) 并且 當(dāng)前無(wú)g處在喚醒態(tài) 并且
            // 有等待鎖的g 并且CAS嘗試將我置為喚醒態(tài)成功 則進(jìn)行自旋
            // 之所以將我置為喚醒態(tài)是為了明示那些執(zhí)行完畢正在退出的g不用再去喚醒其它g了,因?yàn)橹辉试S存在一個(gè)喚醒的g。
			if !awoke && old&mutexWoken == 0 && old>>mutexWaiterShift != 0 &&
				atomic.CompareAndSwapInt32(&m.state, old, old|mutexWoken) {
				awoke = true
			}
			runtime_doSpin()	// 我自旋一次
			iter++				// 我自旋次數(shù)加1
			old = m.state
			continue
		}
		new := old	// new只是個(gè)中間態(tài),后面的cas操作將會(huì)判斷是否將這個(gè)中間態(tài)落實(shí)
		// 如果不是處在饑餓模式就立即搶鎖
		if old&mutexStarving == 0 {
			new |= mutexLocked
		}
        // 如果鎖被搶了 或者 處在饑餓模式,那就去排隊(duì)
		if old&(mutexLocked|mutexStarving) != 0 {
			new += 1 << mutexWaiterShift	// 等待鎖的goroutine數(shù)量加1
		}
		// 如果我現(xiàn)在饑渴難耐 而且 鎖也被搶走了,那就立即將鎖置為饑餓模式
		if starving && old&mutexLocked != 0 {
			new |= mutexStarving
		}
		if awoke {
			if new&mutexWoken == 0 {
				throw("sync: inconsistent mutex state")
			}
            // 釋放我的喚醒態(tài)
            // 因?yàn)楹竺嫖乙磽尩芥i要么被阻塞,都不是處在和喚醒態(tài)
			new &^= mutexWoken
		}
        //此處CAS操作嘗試將new這個(gè)中間態(tài)落實(shí)
		if atomic.CompareAndSwapInt32(&m.state, old, new) {
			if old&(mutexLocked|mutexStarving) == 0 {
				break // 搶鎖成功!
			}
			// queueLifo我之前有沒(méi)有排過(guò)隊(duì)
			queueLifo := waitStartTime != 0
			if waitStartTime == 0 {
				waitStartTime = runtime_nanotime()
			}
            //原語(yǔ):如果我之前排過(guò)隊(duì),這次就把我放到等待隊(duì)列隊(duì)首,否則把我放到隊(duì)尾,并將我掛起
			runtime_SemacquireMutex(&m.sema, queueLifo, 1) 
            // 剛被喚醒的我先判斷自己是不是饑餓了,如果我等待鎖的時(shí)間小于starvationThresholdNs(1ms),那就不餓
			starving = starving || runtime_nanotime()-waitStartTime > starvationThresholdNs
			old = m.state
			if old&mutexStarving != 0 {
				// 我一覺(jué)醒來(lái)發(fā)覺(jué)鎖正處在饑餓狀態(tài),蒼天有眼這個(gè)鎖屬于我了,因?yàn)轲囸I狀態(tài)絕對(duì)沒(méi)有人跟我搶鎖
				if old&(mutexLocked|mutexWoken) != 0 || old>>mutexWaiterShift == 0 {
					throw("sync: inconsistent mutex state")
				}
                // delta是一個(gè)中間狀態(tài),atomic.AddInt32方法將給鎖落實(shí)這個(gè)狀態(tài)
				delta := int32(mutexLocked - 1<<mutexWaiterShift)
				if !starving || old>>mutexWaiterShift == 1 {
                    // 如果現(xiàn)在我不饑餓或者等待鎖的就我一個(gè),那么就將鎖切換到正常狀態(tài)。
                    // 饑餓模式效率很低,而且一旦有兩個(gè)g把mutex切換為饑餓模式,那就會(huì)死鎖。
					delta -= mutexStarving
				}
                // 原語(yǔ):給鎖落實(shí)delta的狀態(tài)。
				atomic.AddInt32(&m.state, delta)
                // 我拿到鎖啦
				break
			}
            // 把我的狀態(tài)置為喚醒,我將繼續(xù)去搶鎖
			awoke = true
            // 把我的自旋次數(shù)置0,我又可以自旋搶鎖啦
			iter = 0
		} else {
            // 繼續(xù)去搶鎖
			old = m.state
		}
	}

    // 競(jìng)爭(zhēng)檢測(cè)的代碼,不管
	if race.Enabled {
		race.Acquire(unsafe.Pointer(m))
	}
}

runtime_canSpin(iter) 判斷當(dāng)前g可否自旋,已經(jīng)自旋過(guò)iter次

func sync_runtime_canSpin(i int) bool {
	// 可自旋的條件:
    // 1.多核cpu
    // 2.GOMAXPROCS > 1 且 至少有一個(gè)其他的p在運(yùn)行 且 該p的本地runq為空
    // 3.iter小于最大自旋次數(shù)active_spin = 4
	if i >= active_spin || ncpu <= 1 || gomaxprocs <= int32(sched.npidle+sched.nmspinning)+1 {
  	return false
	}
  if p := getg().m.p.ptr(); !runqempty(p) {
		return false
	}
	return true
}

runtime_doSpin()通過(guò)調(diào)用procyield(n int32)方法來(lái)實(shí)現(xiàn)空耗CPU,n乃空耗CPU的次數(shù)。

//go:linkname sync_runtime_doSpin sync.runtime_doSpin
//go:nosplit
func sync_runtime_doSpin() {
	procyield(active_spin_cnt)
}

procyield(active_spin_cnt) 的底層通過(guò)執(zhí)行PAUSE指令來(lái)空耗30個(gè)CPU時(shí)鐘周期。

TEXT runtime·procyield(SB),NOSPLIT,$0-0
	MOVL	cycles+0(FP), AX
again:
	PAUSE
	SUBL	$1, AX
	JNZ	again
	RET

runtime_SemacquireMutex(&m.sema, queueLifo, 1) 將當(dāng)前g放到mutex的等待隊(duì)列中去

//go:linkname sync_runtime_SemacquireMutex sync.runtime_SemacquireMutex
func sync_runtime_SemacquireMutex(addr *uint32, lifo bool, skipframes int) {
	semacquire1(addr, lifo, semaBlockProfile|semaMutexProfile, skipframes)
}

semacquire1(addr *uint32, lifo bool, profile semaProfileFlags, skipframes int) 若lifo為true,則把g放到等待隊(duì)列隊(duì)首,若lifo為false,則把g放到隊(duì)尾

atomic.AddInt32(int32_t *val, int32_t delta) 原語(yǔ):給t加上t_delta

uint32_t
AddUint32 (uint32_t *val, uint32_t delta)
{
  return __atomic_add_fetch (val, delta, __ATOMIC_SEQ_CST);
}

解鎖Unlock

Unlock

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

	// 如果沒(méi)有g(shù)在等待鎖則立即釋放鎖
	new := atomic.AddInt32(&m.state, -mutexLocked)
	if new != 0 {
		// 如果還有g(shù)在等待鎖,則在鎖釋放后需要做一點(diǎn)收尾工作。
		m.unlockSlow(new)
	}
}

unlockSlow

func (m *Mutex) unlockSlow(new int32) {
	if (new+mutexLocked)&mutexLocked == 0 {
		throw("sync: unlock of unlocked mutex")
	}
    // 如果鎖處在正常模式下
	if new&mutexStarving == 0 {
		old := new
		for {
			// 如果鎖正處在正常模式下,同時(shí) 沒(méi)有等待鎖的g 或者 已經(jīng)有g(shù)被喚醒了 或者 鎖已經(jīng)被搶了,就什么也不用做直接返回
			// 如果鎖正處在饑餓模式下,也是什么也不用做直接返回
			if old>>mutexWaiterShift == 0 || old&(mutexLocked|mutexWoken|mutexStarving) != 0 {
				return
			}
			// 給鎖的喚醒標(biāo)志位置1,表示已經(jīng)有g(shù)被喚醒了,Mutex.state后三位010
			new = (old - 1<<mutexWaiterShift) | mutexWoken
			if atomic.CompareAndSwapInt32(&m.state, old, new) {
                // 喚醒鎖的等待隊(duì)列頭部的一個(gè)g
                // 并把g放到p的funq尾部
				runtime_Semrelease(&m.sema, false, 1)
				return
			}
			old = m.state
		}
	} else {
		// 鎖處在饑餓模式下,直接喚醒鎖的等待隊(duì)列頭部的一個(gè)g
		//因?yàn)樵陴囸I模式下沒(méi)人跟剛被喚醒的g搶鎖,所以不用設(shè)置鎖的喚醒標(biāo)志位
		runtime_Semrelease(&m.sema, true, 1)
	}
}

runtime_Semrelease(&m.sema, false, 1) 用來(lái)釋放mutex等待隊(duì)列上的一個(gè)g

//go:linkname sync_runtime_Semrelease sync.runtime_Semrelease
func sync_runtime_Semrelease(addr *uint32, handoff bool, skipframes int) {
	semrelease1(addr, handoff, skipframes)
}

semrelease1(addr, handoff, skipframes) 參數(shù)handoff若為true,則讓被喚醒的g立刻繼承當(dāng)前g的時(shí)間片繼續(xù)執(zhí)行。若handoff為false,則把剛被喚醒的g放到當(dāng)前p的runq中。

到此這篇關(guān)于Golang中sync.Mutex的源碼分析 的文章就介紹到這了,更多相關(guān)Golang sync.Mutex內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 解決go build不去vendor下查找包的問(wèn)題

    解決go build不去vendor下查找包的問(wèn)題

    這篇文章主要介紹了解決go build不去vendor下查找包的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2020-12-12
  • Go目錄文件路徑操作的實(shí)現(xiàn)

    Go目錄文件路徑操作的實(shí)現(xiàn)

    在Go語(yǔ)言中,可以使用絕對(duì)路徑或相對(duì)路徑來(lái)表示文件路徑,本文就來(lái)介紹一下Go目錄文件路徑操作,感興趣的可以了解一下
    2023-10-10
  • 如何使用Go語(yǔ)言獲取當(dāng)天、昨天、明天、某天0點(diǎn)時(shí)間戳以及格式化時(shí)間

    如何使用Go語(yǔ)言獲取當(dāng)天、昨天、明天、某天0點(diǎn)時(shí)間戳以及格式化時(shí)間

    這篇文章主要給大家介紹了關(guān)于如何使用Go語(yǔ)言獲取當(dāng)天、昨天、明天、某天0點(diǎn)時(shí)間戳以及格式化時(shí)間的相關(guān)資料,格式化時(shí)間戳是將時(shí)間戳轉(zhuǎn)換為特定的日期和時(shí)間格式,文中通過(guò)代碼示例介紹的非常詳細(xì),需要的朋友可以參考下
    2023-10-10
  • Go與Redis實(shí)現(xiàn)分布式互斥鎖和紅鎖

    Go與Redis實(shí)現(xiàn)分布式互斥鎖和紅鎖

    這篇文章主要介紹了Go與Redis實(shí)現(xiàn)分布式互斥鎖和紅鎖,文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下
    2022-09-09
  • Go語(yǔ)言defer與return執(zhí)行的先后順序詳解

    Go語(yǔ)言defer與return執(zhí)行的先后順序詳解

    這篇文章主要為大家介紹了Go語(yǔ)言defer與return執(zhí)行的先后順序詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-12-12
  • Go設(shè)計(jì)模式之訪問(wèn)者模式講解和代碼示例

    Go設(shè)計(jì)模式之訪問(wèn)者模式講解和代碼示例

    訪問(wèn)者是一種行為設(shè)計(jì)模式, 允許你在不修改已有代碼的情況下向已有類層次結(jié)構(gòu)中增加新的行為,本文將通過(guò)代碼示例給大家詳細(xì)的介紹一下Go設(shè)計(jì)模式之訪問(wèn)者模式,需要的朋友可以參考下
    2023-08-08
  • Go方法接收者值接收者與指針接收者詳解

    Go方法接收者值接收者與指針接收者詳解

    這篇文章主要為大家介紹了Go方法接收者值接收者與指針接收者詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-11-11
  • 一文探索Go中的函數(shù)使用方式

    一文探索Go中的函數(shù)使用方式

    在編程中,函數(shù)是基本構(gòu)建塊之一,Go語(yǔ)言以其簡(jiǎn)潔明了的函數(shù)定義和調(diào)用語(yǔ)法而聞名,所以本文就來(lái)和大家聊聊Go中的函數(shù)概念及使用,感興趣的可以了解下
    2023-09-09
  • Go打包靜態(tài)文件的兩種方式

    Go打包靜態(tài)文件的兩種方式

    使用 Go 開(kāi)發(fā)應(yīng)用的時(shí)候,有時(shí)會(huì)遇到需要讀取靜態(tài)資源的情況,如果不打包處理這種靜態(tài)文件:發(fā)布單獨(dú)掛載這種靜態(tài)文件相對(duì)比較麻煩,就有人會(huì)想辦法把靜態(tài)資源文件打包進(jìn) Go 的程序文件中,下面介紹兩種打包方式:go-bindata、go:embed,需要的朋友可以參考下
    2024-04-04
  • Go/C語(yǔ)言LeetCode題解997找到小鎮(zhèn)法官

    Go/C語(yǔ)言LeetCode題解997找到小鎮(zhèn)法官

    這篇文章主要為大家介紹了Go語(yǔ)言LeetCode題解997找到小鎮(zhèn)的法官示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-12-12

最新評(píng)論