最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Golang Mutex實(shí)現(xiàn)互斥的具體方法

 更新時(shí)間:2023年04月24日 09:56:42   作者:動(dòng)態(tài)一時(shí)爽,重構(gòu)火葬場(chǎng)  
Mutex是Golang常見(jiàn)的并發(fā)原語(yǔ),在開(kāi)發(fā)過(guò)程中經(jīng)常使用到,本文主要介紹了Golang Mutex實(shí)現(xiàn)互斥的具體方法,具有一定的參考價(jià)值,感興趣的可以了解一下

Mutex是Golang常見(jiàn)的并發(fā)原語(yǔ),不僅在開(kāi)發(fā)過(guò)程中經(jīng)常使用到,如channel這種具有g(shù)olang特色的并發(fā)結(jié)構(gòu)也依托于Mutex從而實(shí)現(xiàn)

type Mutex struct {
  // 互斥鎖的狀態(tài),比如是否被鎖定
  state int32
  // 表示信號(hào)量。堵塞的協(xié)程會(huì)等待該信號(hào)量,解鎖的協(xié)程會(huì)釋放該信號(hào)量
  sema int32
}
const (
? // 當(dāng)前是否已經(jīng)上鎖
? mutexLocked = 1 << iota // 1
? // 當(dāng)前是否有喚醒的goroutine
? mutexWoken // 2
? // 當(dāng)前是否為饑餓狀態(tài)
? mutexStarving // 4
? // state >> mutexWaiterShift 得到等待者數(shù)量
? mutexWaiterShift = iota // 3

? starvationThresholdNs = 1e6 // 判斷是否要進(jìn)入饑餓狀態(tài)的閾值
)

Mutex有正常饑餓模式。

  • 正常模式:等待者會(huì)入隊(duì),但一個(gè)喚醒的等待者不能持有鎖,以及與新到來(lái)的goroutine進(jìn)行競(jìng)爭(zhēng)。新來(lái)的goroutine有一個(gè)優(yōu)勢(shì)——他們已經(jīng)運(yùn)行在CPU上。
    超過(guò)1ms沒(méi)有獲取到鎖,就會(huì)進(jìn)入饑餓模式
  • 饑餓模式:鎖的所有權(quán)直接移交給隊(duì)列頭goroutine,新來(lái)的goroutine不會(huì)嘗試獲取互斥鎖,即使互斥鎖看起來(lái)已經(jīng)解鎖,也不會(huì)嘗試旋轉(zhuǎn)。相反,他們自己排在等待隊(duì)列的末尾。

若等待者是最后一個(gè),或者等待小于1ms就會(huì)切換回正常模式

獲取鎖

未鎖——直接獲取

func (m *Mutex) Lock() {
    // 快路徑。直接獲取未鎖的mutex
  // 初始狀態(tài)為0,所以只要狀態(tài)存在其他任何狀態(tài)位都是無(wú)法直接獲取的
    if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {
        if race.Enabled {
            race.Acquire(unsafe.Pointer(m))
        }
        return
    }
    // Slow path (outlined so that the fast path can be inlined)
    m.lockSlow()
}

在不饑餓且旋的不多的情況下,嘗試自旋

        // 只要原狀態(tài)已鎖且不處于饑餓狀態(tài),并滿(mǎn)足自旋條件
        if old&(mutexLocked|mutexStarving) == mutexLocked && runtime_canSpin(iter) {
            // 在當(dāng)前goroutine沒(méi)有喚醒,且沒(méi)有其他goroutine在嘗試喚醒,且存在等待的情況下,cas標(biāo)記存在goroutine正在嘗試喚醒。若標(biāo)記成功就設(shè)置當(dāng)前goroutine已經(jīng)喚醒了
            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
        }

具體的自旋條件如下

  • 自旋次數(shù)小于4
  • 多核CPU
  • p數(shù)量大于1
  • 至少存在一個(gè)p的隊(duì)列為空
const (
?? ?locked uintptr = 1

?? ?active_spin ? ? = 4
?? ?active_spin_cnt = 30
?? ?passive_spin ? ?= 1
)

func sync_runtime_canSpin(i int) bool {
?? ?// sync.Mutex is cooperative, so we are conservative with spinning.
?? ?// Spin only few times and only if running on a multicore machine and
?? ?// GOMAXPROCS>1 and there is at least one other running P and local runq is empty.
?? ?// As opposed to runtime mutex we don't do passive spinning here,
?? ?// because there can be work on global runq or on other Ps.
?? ?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()執(zhí)行的,實(shí)際調(diào)用了procyield()

# 定義了一個(gè)runtime.procyield的文本段,通過(guò)NOSPLIT不使用棧分裂,$0-0 表示該函數(shù)不需要任何輸入?yún)?shù)和返回值
TEXT runtime·procyield(SB),NOSPLIT,$0-0
    # 從棧幀中讀取cycles參賽值,并儲(chǔ)存在寄存器R0中
    MOVWU    cycles+0(FP), R0
# 組成無(wú)限循環(huán)。在每次循環(huán)中,通過(guò)YIELD告訴CPU將當(dāng)前線(xiàn)程置于休眠狀態(tài)
# YIELD: x86上,實(shí)現(xiàn)為PAUSE指令,會(huì)暫停處理器執(zhí)行,切換CPU到低功耗模式并等待更多數(shù)據(jù)到達(dá)。通常用于忙等待機(jī)制,避免無(wú)謂CPU占用
# ARM上,實(shí)現(xiàn)為WFE(Wait For Event),用于等待中斷或者其他事件發(fā)生。在某些情況下可能會(huì)導(dǎo)致CPU陷入死循環(huán),因此需要特殊處理邏輯解決
again:
    YIELD
    # 將R0值減1
    SUBW    $1, R0
    # CBNZ(Compare and Branch on Non-Zero)檢查剩余的時(shí)鐘周期數(shù)是否為0。不為0就跳轉(zhuǎn)到標(biāo)簽again并再次調(diào)用YIELD,否則就退出函數(shù)
    CBNZ    R0, again
    RET

以上匯編代碼分析過(guò)程感謝chatgpt的大力支持

從代碼中可以看到自旋次數(shù)是30次

const active_spin_cnt = 30

func sync_runtime_doSpin() {
?? ?procyield(active_spin_cnt)
}

計(jì)算期望狀態(tài)

1.原狀態(tài)不處于饑餓狀態(tài),新?tīng)顟B(tài)設(shè)置已鎖狀態(tài)位

原狀態(tài)處于已鎖狀態(tài)或饑餓模式,新?tīng)顟B(tài)設(shè)置等待數(shù)量遞增

當(dāng)前goroutine是最新獲取鎖的goroutine,在正常模式下期望就是要獲取鎖,那么就應(yīng)該設(shè)置新?tīng)顟B(tài)已鎖狀態(tài)位

如果鎖已經(jīng)被搶占了,或者處于饑餓模式,那么就應(yīng)該去排隊(duì)

2.若之前嘗試獲取時(shí)已經(jīng)超過(guò)饑餓閾值時(shí)間,且原狀態(tài)已鎖,那么新?tīng)顟B(tài)設(shè)置饑餓狀態(tài)位

3.若goroutine處于喚醒,則新?tīng)顟B(tài)清除正在喚醒狀態(tài)位

期望是已經(jīng)獲取到鎖了,那么自然要清除正在獲取的狀態(tài)位

        new := old
        // Don't try to acquire starving mutex, new arriving goroutines must queue.
    // 若原狀態(tài)不處于饑餓狀態(tài),就給新?tīng)顟B(tài)設(shè)置已加鎖
        if old&mutexStarving == 0 {
            new |= mutexLocked
        }
    // 只要原狀態(tài)處于已鎖或者饑餓模式,就將新?tīng)顟B(tài)等待數(shù)量遞增
        if old&(mutexLocked|mutexStarving) != 0 {
            new += 1 << mutexWaiterShift
        }
    // 若已經(jīng)等待超過(guò)饑餓閾值時(shí)間且原狀態(tài)已鎖,就設(shè)置新?tīng)顟B(tài)為饑餓
    // 這也意味著如果已經(jīng)不處于已鎖狀態(tài),就可以切換回正常模式了
        if starving && old&mutexLocked != 0 {
            new |= mutexStarving
        }
    // 如果已經(jīng)喚醒了(也就是沒(méi)有其他正在搶占的goroutine),則在新?tīng)顟B(tài)中清除正在喚醒狀態(tài)位
        if awoke {
            // The goroutine has been woken from sleep,
            // so we need to reset the flag in either case.
            if new&mutexWoken == 0 {
                throw("sync: inconsistent mutex state")
            }
            new &^= mutexWoken
        }

嘗試達(dá)成獲取鎖期望

cas嘗試從原狀態(tài)更新為新的期望狀態(tài)

如果失敗,則更新最新?tīng)顟B(tài),繼續(xù)嘗試獲取鎖

說(shuō)明這期間鎖已經(jīng)被搶占了

若原來(lái)既沒(méi)有被鎖住,也沒(méi)有處于饑餓模式,那么就獲取到鎖,直接返回

排隊(duì)。若之前已經(jīng)在等待了就排到隊(duì)列頭

獲取信號(hào)量。此處會(huì)堵塞等待

被喚醒,認(rèn)定已經(jīng)持有鎖。并做以下饑餓相關(guān)處理

  • 計(jì)算等待時(shí)長(zhǎng),若超出饑餓閾值時(shí)間,就標(biāo)記當(dāng)前goroutine處于饑餓
  • 若鎖處于饑餓模式,遞減等待數(shù)量,并且在只有一個(gè)等待的時(shí)候,切換鎖回正常模式
if atomic.CompareAndSwapInt32(&m.state, old, new) {
      // 如果原狀態(tài)既沒(méi)有處于已鎖狀態(tài),也沒(méi)有處于饑餓模式
      // 那么就表示已經(jīng)獲取到鎖,直接退出
            if old&(mutexLocked|mutexStarving) == 0 {
                break // locked the mutex with CAS
            }
      // 若已經(jīng)在等待了,就排到隊(duì)列頭
            queueLifo := waitStartTime != 0
            if waitStartTime == 0 {
                waitStartTime = runtime_nanotime()
            }
      // 嘗試獲取信號(hào)量。此處獲取一個(gè)信號(hào)量以實(shí)現(xiàn)互斥
      // 此處會(huì)進(jìn)行堵塞
            runtime_SemacquireMutex(&m.sema, queueLifo, 1)
      // 被信號(hào)量喚醒之后,發(fā)現(xiàn)若等待時(shí)間超過(guò)饑餓閾值,就切換到饑餓模式
            starving = starving || runtime_nanotime()-waitStartTime > starvationThresholdNs
            old = m.state
      // 處于饑餓模式下
            if old&mutexStarving != 0 {
                // If this goroutine was woken and mutex is in starvation mode,
                // ownership was handed off to us but mutex is in somewhat
                // inconsistent state: mutexLocked is not set and we are still
                // accounted as waiter. Fix that.
        // 若既沒(méi)有已鎖且正在嘗試喚醒,或者等待隊(duì)列為空,就代表產(chǎn)生了不一致的狀態(tài)
                if old&(mutexLocked|mutexWoken) != 0 || old>>mutexWaiterShift == 0 {
                    throw("sync: inconsistent mutex state")
                }
        // 當(dāng)前goroutine已經(jīng)獲取鎖,等待隊(duì)列減1;若等待者就一個(gè),就切換正常模式。退出
                delta := int32(mutexLocked - 1<<mutexWaiterShift)
                if !starving || old>>mutexWaiterShift == 1 {
                    delta -= mutexStarving
                }
                atomic.AddInt32(&m.state, delta)
                break
            }
      // 不處于饑餓模式下,設(shè)置當(dāng)前goroutine為喚醒狀態(tài),重置自璇次數(shù),繼續(xù)嘗試獲取鎖
            awoke = true
            iter = 0
        } else {
      // 若鎖被其他goroutine占用了,就更新原狀態(tài),繼續(xù)嘗試獲取鎖
            old = m.state
        }

考慮幾種場(chǎng)景

  • 如果lock當(dāng)前只有一個(gè)goroutine g1去獲取鎖,那么會(huì)直接快路徑,cas更新已鎖狀態(tài)位,獲取到鎖
  • 如果鎖已經(jīng)被g1持有,
    • 此時(shí)g2會(huì)先自旋4次,
    • 然后計(jì)算期望狀態(tài)為已鎖、等待數(shù)量為1、喚醒狀態(tài)位被清除
    • 在cas更新的時(shí)候嘗試更新鎖狀態(tài)成功,接著因?yàn)樵瓲顟B(tài)本身處于已鎖,所以就不能獲取到鎖,只能排隊(duì),信號(hào)量堵塞
    • g1釋放鎖后,g2被喚醒,接著再次計(jì)算期望狀態(tài),并cas更新?tīng)顟B(tài)成功,直接獲取到鎖
  • 如果鎖已經(jīng)被g1持有,且g2在第一次嘗試獲取時(shí)超過(guò)了1ms(也就是饑餓閾值),那么
    • 計(jì)算期望狀態(tài)為已鎖、饑餓、清除喚醒狀態(tài)位
    • cas更新?tīng)顟B(tài)成功,排在隊(duì)列頭,并被信號(hào)量堵塞
    • g1釋放鎖后,g2被喚醒就直接獲取到鎖,并減去排隊(duì)數(shù)量以及清空饑餓位

釋放鎖

只有已鎖——直接釋放

如果沒(méi)有排隊(duì)的goroutine,沒(méi)有處于饑餓狀態(tài),也沒(méi)有真正嘗試獲取鎖的goroutine,那么就可以直接cas更新?tīng)顟B(tài)為0

func (m *Mutex) Unlock() {
    // Fast path: drop lock bit.
    new := atomic.AddInt32(&m.state, -mutexLocked)
    if new != 0 {
        // Outlined slow path to allow inlining the fast path.
        // To hide unlockSlow during tracing we skip one extra frame when tracing GoUnblock.
        m.unlockSlow(new)
    }
}

慢釋放

  • 如果原鎖沒(méi)有被鎖住,就報(bào)錯(cuò)
  • 若原狀態(tài)不處于饑餓狀態(tài),嘗試喚醒等待者
    • 若現(xiàn)在鎖已經(jīng)被獲取、正在獲取、饑餓或者沒(méi)有等待者,直接返回
    • 期望狀態(tài)等待數(shù)量減1,并設(shè)置正在喚醒狀態(tài)位
    • cas嘗試更新期望狀態(tài),若成功,釋放
    • 失敗說(shuō)明在這過(guò)程中又有g(shù)oroutine在嘗試獲取,那么繼續(xù)下一輪釋放
  • 處于饑餓狀態(tài),直接釋放信號(hào)量,移交鎖所有權(quán)
func (m *Mutex) unlockSlow(new int32) {
  // 若原狀態(tài)根本沒(méi)有已鎖狀態(tài)位
    if (new+mutexLocked)&mutexLocked == 0 {
        throw("sync: unlock of unlocked mutex")
    }
  // 若原狀態(tài)不處于饑餓狀態(tài)
    if new&mutexStarving == 0 {
        old := new
        for {
      // 若沒(méi)有等待,或者存在goroutine已經(jīng)被喚醒,或者已經(jīng)被鎖住了,就不需要喚醒任何人,返回
            if old>>mutexWaiterShift == 0 || old&(mutexLocked|mutexWoken|mutexStarving) != 0 {
                return
            }
            // Grab the right to wake someone.
      // 設(shè)置期望狀態(tài)為正在獲取狀態(tài)位,并減去一個(gè)等待者
            new = (old - 1<<mutexWaiterShift) | mutexWoken
      // 嘗試cas更新為期望新?tīng)顟B(tài),若成功就釋放信號(hào)量,失敗就更新原狀態(tài),進(jìn)行下一輪釋放
      // 失敗說(shuō)明在這過(guò)程中又有g(shù)oroutine在嘗試獲取,比如已經(jīng)獲取到了、變成饑餓了、自旋等
            if atomic.CompareAndSwapInt32(&m.state, old, new) {
                runtime_Semrelease(&m.sema, false, 1)
                return
            }
            old = m.state
        }
    } else {
    // 饑餓模式下就移交鎖所有權(quán)給下一個(gè)等待者,并放棄時(shí)間片,以便該等待者可以快速開(kāi)始
        runtime_Semrelease(&m.sema, true, 1)
    }
}

到此這篇關(guān)于Golang Mutex實(shí)現(xiàn)互斥的具體方法的文章就介紹到這了,更多相關(guān)Golang Mutex互斥內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Go 循環(huán)結(jié)構(gòu)for循環(huán)使用教程全面講解

    Go 循環(huán)結(jié)構(gòu)for循環(huán)使用教程全面講解

    這篇文章主要為大家介紹了Go 循環(huán)結(jié)構(gòu)for循環(huán)使用全面講解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-10-10
  • golang如何用type-switch判斷interface變量的實(shí)際存儲(chǔ)類(lèi)型

    golang如何用type-switch判斷interface變量的實(shí)際存儲(chǔ)類(lèi)型

    這篇文章主要介紹了golang如何用type-switch判斷interface變量的實(shí)際存儲(chǔ)類(lèi)型,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-04-04
  • Go語(yǔ)言實(shí)戰(zhàn)之實(shí)現(xiàn)一個(gè)簡(jiǎn)單分布式系統(tǒng)

    Go語(yǔ)言實(shí)戰(zhàn)之實(shí)現(xiàn)一個(gè)簡(jiǎn)單分布式系統(tǒng)

    如今很多云原生系統(tǒng)、分布式系統(tǒng),例如?Kubernetes,都是用?Go?語(yǔ)言寫(xiě)的,這是因?yàn)?Go?語(yǔ)言天然支持異步編程。本篇文章將介紹如何用?Go?語(yǔ)言編寫(xiě)一個(gè)簡(jiǎn)單的分布式系統(tǒng),需要的小伙伴開(kāi)業(yè)跟隨小編一起學(xué)習(xí)一下
    2022-10-10
  • Go?CSV包實(shí)現(xiàn)結(jié)構(gòu)體和csv內(nèi)容互轉(zhuǎn)工具詳解

    Go?CSV包實(shí)現(xiàn)結(jié)構(gòu)體和csv內(nèi)容互轉(zhuǎn)工具詳解

    這篇文章主要介紹了Go?CSV包實(shí)現(xiàn)結(jié)構(gòu)體和csv內(nèi)容互轉(zhuǎn)工具詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-03-03
  • Go語(yǔ)言Echo服務(wù)器的方法

    Go語(yǔ)言Echo服務(wù)器的方法

    這篇文章主要介紹了Go語(yǔ)言Echo服務(wù)器的方法,實(shí)例分析了Echo服務(wù)器的技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下
    2015-02-02
  • 基于GORM實(shí)現(xiàn)CreateOrUpdate方法詳解

    基于GORM實(shí)現(xiàn)CreateOrUpdate方法詳解

    這篇文章主要為大家介紹了基于GORM實(shí)現(xiàn)CreateOrUpdate方法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-10-10
  • golang之判斷元素是否在數(shù)組內(nèi)問(wèn)題

    golang之判斷元素是否在數(shù)組內(nèi)問(wèn)題

    這篇文章主要介紹了golang之判斷元素是否在數(shù)組內(nèi)問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2020-12-12
  • golang常用庫(kù)之操作數(shù)據(jù)庫(kù)的orm框架-gorm基本使用詳解

    golang常用庫(kù)之操作數(shù)據(jù)庫(kù)的orm框架-gorm基本使用詳解

    這篇文章主要介紹了golang常用庫(kù)之操作數(shù)據(jù)庫(kù)的orm框架-gorm基本使用,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-10-10
  • golang中interface接口的深度解析

    golang中interface接口的深度解析

    什么是interface,簡(jiǎn)單的說(shuō),interface是一組method的組合,下面這篇文章主要給大家深度解析了關(guān)于golang中的interface接口,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。
    2017-11-11
  • GoLang之標(biāo)準(zhǔn)庫(kù)encoding/json包

    GoLang之標(biāo)準(zhǔn)庫(kù)encoding/json包

    本文主要介紹了GoLang之標(biāo)準(zhǔn)庫(kù)encoding/json包,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2022-08-08

最新評(píng)論

盐边县| 云和县| 焦作市| 女性| 兴文县| 石狮市| 汉川市| 柯坪县| 巴林左旗| 平邑县| 鄱阳县| 稻城县| 辽阳县| 廊坊市| 长岭县| 凌源市| 搜索| 南城县| 和平县| 加查县| 平昌县| 兴文县| 眉山市| 湘阴县| 铁岭县| 大荔县| 平邑县| 女性| 定结县| 东乡县| 高雄市| 蕲春县| 军事| 南华县| 霍林郭勒市| 阿荣旗| 开平市| 云林县| 漳州市| 浦县| 沙湾县|