<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          面試官:Go 語(yǔ)言互斥鎖了解到什么程度?

          共 14362字,需瀏覽 29分鐘

           ·

          2022-07-16 01:20

          前言

          當(dāng)提到并發(fā)編程、多線程編程時(shí),都會(huì)在第一時(shí)間想到鎖,鎖是并發(fā)編程中的同步原語(yǔ),他可以保證多線程在訪問(wèn)同一片內(nèi)存時(shí)不會(huì)出現(xiàn)競(jìng)爭(zhēng)來(lái)保證并發(fā)安全;在Go語(yǔ)言中更推崇由channel通過(guò)通信的方式實(shí)現(xiàn)共享內(nèi)存,這個(gè)設(shè)計(jì)點(diǎn)與許多主流編程語(yǔ)言不一致,但是Go語(yǔ)言也在sync包中提供了互斥鎖、讀寫(xiě)鎖,畢竟channel也不能滿足所有場(chǎng)景,互斥鎖、讀寫(xiě)鎖的使用與我們是分不開(kāi)的,所以接下來(lái)我會(huì)分兩篇來(lái)分享互斥鎖、讀寫(xiě)鎖是怎么實(shí)現(xiàn)的,本文我們先來(lái)看看互斥鎖的實(shí)現(xiàn)。

          本文基于Golang版本:1.18

          Go語(yǔ)言互斥鎖設(shè)計(jì)實(shí)現(xiàn)

          mutex介紹

          sync 包下的mutex就是互斥鎖,其提供了三個(gè)公開(kāi)方法:調(diào)用Lock()獲得鎖,調(diào)用Unlock()釋放鎖,在Go1.18新提供了TryLock()方法可以非阻塞式的取鎖操作:

          • Lock():調(diào)用Lock方法進(jìn)行加鎖操作,使用時(shí)應(yīng)注意在同一個(gè)goroutine中必須在鎖釋放時(shí)才能再次上鎖,否則會(huì)導(dǎo)致程序panic
          • Unlock():調(diào)用UnLock方法進(jìn)行解鎖操作,使用時(shí)應(yīng)注意未加鎖的時(shí)候釋放鎖會(huì)引起程序panic,已經(jīng)鎖定的 Mutex 并不與特定的 goroutine 相關(guān)聯(lián),這樣可以利用一個(gè) goroutine 對(duì)其加鎖,再利用其他 goroutine 對(duì)其解鎖。
          • tryLock():調(diào)用TryLock方法嘗試獲取鎖,當(dāng)鎖被其他 goroutine 占有,或者當(dāng)前鎖正處于饑餓模式,它將立即返回 false,當(dāng)鎖可用時(shí)嘗試獲取鎖,獲取失敗不會(huì)自旋/阻塞,也會(huì)立即返回false;

          mutex的結(jié)構(gòu)比較簡(jiǎn)單只有兩個(gè)字段:

          type Mutex struct {
           state int32
           sema  uint32
          }
          • state:表示當(dāng)前互斥鎖的狀態(tài),復(fù)合型字段;
          • sema:信號(hào)量變量,用來(lái)控制等待goroutine的阻塞休眠和喚醒

          初看結(jié)構(gòu)你可能有點(diǎn)懵逼,互斥鎖應(yīng)該是一個(gè)復(fù)雜東西,怎么就兩個(gè)字段就可以實(shí)現(xiàn)?那是因?yàn)樵O(shè)計(jì)使用了位的方式來(lái)做標(biāo)志,state的不同位分別表示了不同的狀態(tài),使用最小的內(nèi)存來(lái)表示更多的意義,其中低三位由低到高分別表示mutexedmutexWokenmutexStarving,剩下的位則用來(lái)表示當(dāng)前共有多少個(gè)goroutine在等待鎖:

          const (
             mutexLocked = 1 << iota // 表示互斥鎖的鎖定狀態(tài)
             mutexWoken // 表示從正常模式被從喚醒
             mutexStarving // 當(dāng)前的互斥鎖進(jìn)入饑餓狀態(tài)
             mutexWaiterShift = iota // 當(dāng)前互斥鎖上等待者的數(shù)量
          )
          截屏2022-06-26 下午12.08.46

          mutex最開(kāi)始的實(shí)現(xiàn)只有正常模式,在正常模式下等待的線程按照先進(jìn)先出的方式獲取鎖,但是新創(chuàng)建的gouroutine會(huì)與剛被喚起的 goroutine競(jìng)爭(zhēng),會(huì)導(dǎo)致剛被喚起的 goroutine獲取不到鎖,這種情況的出現(xiàn)會(huì)導(dǎo)致線程長(zhǎng)時(shí)間被阻塞下去,所以Go語(yǔ)言在1.9中進(jìn)行了優(yōu)化,引入了饑餓模式,當(dāng)goroutine超過(guò)1ms沒(méi)有獲取到鎖,就會(huì)將當(dāng)前互斥鎖切換到饑餓模式,在饑餓模式中,互斥鎖會(huì)直接交給等待隊(duì)列最前面的goroutine,新的 goroutine 在該狀態(tài)下不能獲取鎖、也不會(huì)進(jìn)入自旋狀態(tài),它們只會(huì)在隊(duì)列的末尾等待。如果一個(gè) goroutine 獲得了互斥鎖并且它在隊(duì)列的末尾或者它等待的時(shí)間少于 1ms,那么當(dāng)前的互斥鎖就會(huì)切換回正常模式。

          mutex的基本情況大家都已經(jīng)掌握了,接下來(lái)我們從加鎖到解鎖來(lái)分析mutex是如何實(shí)現(xiàn)的;

          Lock加鎖

          Lock方法入手:

          func (m *Mutex) Lock() {
           // 判斷當(dāng)前鎖的狀態(tài),如果鎖是完全空閑的,即m.state為0,則對(duì)其加鎖,將m.state的值賦為1
           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()
          }

          上面的代碼主要兩部分邏輯:

          • 通過(guò)CAS判斷當(dāng)前鎖的狀態(tài),也就是state字段的低1位,如果鎖是完全空閑的,即m.state為0,則對(duì)其加鎖,將m.state的值賦為1
          • 若當(dāng)前鎖已經(jīng)被其他goroutine加鎖,則進(jìn)行lockSlow方法嘗試通過(guò)自旋或饑餓狀態(tài)下饑餓goroutine競(jìng)爭(zhēng)方式等待鎖的釋放,我們?cè)谙旅娼榻BlockSlow方法;

          lockSlow代碼段有點(diǎn)長(zhǎng),主體是一個(gè)for循環(huán),其主要邏輯可以分為以下三部分:

          • 狀態(tài)初始化
          • 判斷是否符合自旋條件,符合條件進(jìn)行自旋操作
          • 搶鎖準(zhǔn)備期望狀態(tài)
          • 通過(guò)CAS操作更新期望狀態(tài)

          初始化狀態(tài)

          locakSlow方法內(nèi)會(huì)先初始化5個(gè)字段:

          func (m *Mutex) lockSlow() {
           var waitStartTime int64 
           starving := false
           awoke := false
           iter := 0
           old := m.state
           ........
          }
          • waitStartTime用來(lái)計(jì)算waiter的等待時(shí)間
          • starving是饑餓模式標(biāo)志,如果等待時(shí)長(zhǎng)超過(guò)1ms,starving置為true,后續(xù)操作會(huì)把Mutex也標(biāo)記為饑餓狀態(tài)。
          • awoke表示協(xié)程是否喚醒,當(dāng)goroutine在自旋時(shí),相當(dāng)于CPU上已經(jīng)有在等鎖的協(xié)程。為避免Mutex解鎖時(shí)再喚醒其他協(xié)程,自旋時(shí)要嘗試把Mutex置為喚醒狀態(tài),Mutex處于喚醒狀態(tài)后 要把本協(xié)程的 awoke 也置為true。
          • iter用于記錄協(xié)程的自旋次數(shù),
          • old記錄當(dāng)前鎖的狀態(tài)

          自旋

          自旋的判斷條件非常苛刻:

          for {
              // 判斷是否允許進(jìn)入自旋 兩個(gè)條件,條件1是當(dāng)前鎖不能處于饑餓狀態(tài)
              // 條件2是在runtime_canSpin內(nèi)實(shí)現(xiàn),其邏輯是在多核CPU運(yùn)行,自旋的次數(shù)小于4
            if old&(mutexLocked|mutexStarving) == mutexLocked && runtime_canSpin(iter) {
                // !awoke 判斷當(dāng)前goroutine不是在喚醒狀態(tài)
                // old&mutexWoken == 0 表示沒(méi)有其他正在喚醒的goroutine
                // old>>mutexWaiterShift != 0 表示等待隊(duì)列中有正在等待的goroutine
                // atomic.CompareAndSwapInt32(&m.state, old, old|mutexWoken) 嘗試將當(dāng)前鎖的低2位的Woken狀態(tài)位設(shè)置為1,表示已被喚醒, 這是為了通知在解鎖Unlock()中不要再喚醒其他的waiter了
             if !awoke && old&mutexWoken == 0 && old>>mutexWaiterShift != 0 &&
              atomic.CompareAndSwapInt32(&m.state, old, old|mutexWoken) {
               // 設(shè)置當(dāng)前goroutine喚醒成功
                    awoke = true
             }
                // 進(jìn)行自旋
             runtime_doSpin()
                // 自旋次數(shù)
             iter++
                // 記錄當(dāng)前鎖的狀態(tài)
             old = m.state
             continue
            }
          }

          自旋這里的條件還是很復(fù)雜的,我們想讓當(dāng)前goroutine進(jìn)入自旋轉(zhuǎn)的原因是我們樂(lè)觀的認(rèn)為當(dāng)前正在持有鎖的goroutine能在較短的時(shí)間內(nèi)歸還鎖,所以我們需要一些條件來(lái)判斷,mutex的判斷條件我們?cè)谖淖置枋鲆幌拢?/p>

          old&(mutexLocked|mutexStarving) == mutexLocked 用來(lái)判斷鎖是否處于正常模式且加鎖,為什么要這么判斷呢?

          mutexLocked 二進(jìn)制表示為 0001

          mutexStarving 二進(jìn)制表示為 0100

          mutexLocked|mutexStarving 二進(jìn)制為 0101. 使用0101在當(dāng)前狀態(tài)做 &操作,如果當(dāng)前處于饑餓模式,低三位一定會(huì)是1,如果當(dāng)前處于加鎖模式,低1位一定會(huì)是1,所以使用該方法就可以判斷出當(dāng)前鎖是否處于正常模式且加鎖;

          runtime_canSpin()方法用來(lái)判斷是否符合自旋條件:

          // / go/go1.18/src/runtime/proc.go
          const active_spin     = 4
          func sync_runtime_canSpin(i int) bool {
           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
          }

          自旋條件如下:

          • 自旋的次數(shù)要在4次以內(nèi)
          • CPU必須為多核
          • GOMAXPROCS>1
          • 當(dāng)前機(jī)器上至少存在一個(gè)正在運(yùn)行的處理器 P 并且處理的運(yùn)行隊(duì)列為空;

          判斷當(dāng)前goroutine可以進(jìn)自旋后,調(diào)用runtime_doSpin方法進(jìn)行自旋:

          const active_spin_cnt = 30
          func sync_runtime_doSpin() {
           procyield(active_spin_cnt)
          }
          // asm_amd64.s
          TEXT runtime·procyield(SB),NOSPLIT,$0-0
           MOVL cycles+0(FP), AX
          again:
           PAUSE
           SUBL $1, AX
           JNZ again
           RET

          循環(huán)次數(shù)被設(shè)置為30次,自旋操作就是執(zhí)行30次PAUSE指令,通過(guò)該指令占用CPU并消費(fèi)CPU時(shí)間,進(jìn)行忙等待;

          這就是整個(gè)自旋操作的邏輯,這個(gè)就是為了優(yōu)化 等待阻塞->喚醒->參與搶占鎖這個(gè)過(guò)程不高效,所以使用自旋進(jìn)行優(yōu)化,在期望在這個(gè)過(guò)程中鎖被釋放。

          搶鎖準(zhǔn)備期望狀態(tài)

          自旋邏輯處理好后開(kāi)始根據(jù)上下文計(jì)算當(dāng)前互斥鎖最新的狀態(tài),根據(jù)不同的條件來(lái)計(jì)算mutexLockedmutexStarvingmutexWokenmutexWaiterShift

          首先計(jì)算mutexLocked的值:

              // 基于old狀態(tài)聲明到一個(gè)新?tīng)顟B(tài)
            new := old
            // 新?tīng)顟B(tài)處于非饑餓的條件下才可以加鎖
            if old&mutexStarving == 0 {
             new |= mutexLocked
            }

          計(jì)算mutexWaiterShift的值:

          //如果old已經(jīng)處于加鎖或者饑餓狀態(tài),則等待者按照FIFO的順序排隊(duì)
          if old&(mutexLocked|mutexStarving) != 0 {
             new += 1 << mutexWaiterShift
            }

          計(jì)算mutexStarving的值:

          // 如果當(dāng)前鎖處于饑餓模式,并且已被加鎖,則將低3位的Starving狀態(tài)位設(shè)置為1,表示饑餓
          if starving && old&mutexLocked != 0 {
             new |= mutexStarving
            }

          計(jì)算mutexWoken的值:

          // 當(dāng)前goroutine的waiter被喚醒,則重置flag
          if awoke {
             // 喚醒狀態(tài)不一致,直接拋出異常
             if new&mutexWoken == 0 {
              throw("sync: inconsistent mutex state")
             }
               // 新?tīng)顟B(tài)清除喚醒標(biāo)記,因?yàn)楹竺娴膅oroutine只會(huì)阻塞或者搶鎖成功
               // 如果是掛起狀態(tài),那就需要等待其他釋放鎖的goroutine來(lái)喚醒。
               // 假如其他goroutine在unlock的時(shí)候發(fā)現(xiàn)Woken的位置不是0,則就不會(huì)去喚醒,那該goroutine就無(wú)法在被喚醒后加鎖
             new &^= mutexWoken
          }

          通過(guò)CAS操作更新期望狀態(tài)

          上面我們已經(jīng)得到了鎖的期望狀態(tài),接下來(lái)通過(guò)CAS將鎖的狀態(tài)進(jìn)行更新:

          // 這里嘗試將鎖的狀態(tài)更新為期望狀態(tài)
          if atomic.CompareAndSwapInt32(&m.state, old, new) {
            // 如果原來(lái)鎖的狀態(tài)是沒(méi)有加鎖的并且不處于饑餓狀態(tài),則表示當(dāng)前goroutine已經(jīng)獲取到鎖了,直接推出即可
             if old&(mutexLocked|mutexStarving) == 0 {
              break // locked the mutex with CAS
             }
             // 到這里就表示goroutine還沒(méi)有獲取到鎖,waitStartTime是goroutine開(kāi)始等待的時(shí)間,waitStartTime != 0就表示當(dāng)前goroutine已經(jīng)等待過(guò)了,則需要將其放置在等待隊(duì)列隊(duì)頭,否則就排到隊(duì)列隊(duì)尾
             queueLifo := waitStartTime != 0
             if waitStartTime == 0 {
              waitStartTime = runtime_nanotime()
             }
                // 阻塞等待
             runtime_SemacquireMutex(&m.sema, queueLifo, 1)
                // 被信號(hào)量喚醒后檢查當(dāng)前goroutine是否應(yīng)該表示為饑餓
               // 1. 當(dāng)前goroutine已經(jīng)饑餓
               // 2. goroutine已經(jīng)等待了1ms以上
             starving = starving || runtime_nanotime()-waitStartTime > starvationThresholdNs
            // 再次獲取當(dāng)前鎖的狀態(tài)
             old = m.state
             // 如果當(dāng)前處于饑餓模式,
             if old&mutexStarving != 0 {
                  // 如果當(dāng)前鎖既不是被獲取也不是被喚醒狀態(tài),或者等待隊(duì)列為空 這代表鎖狀態(tài)產(chǎn)生了不一致的問(wèn)題
              if old&(mutexLocked|mutexWoken) != 0 || old>>mutexWaiterShift == 0 {
               throw("sync: inconsistent mutex state")
              }
                  // 當(dāng)前goroutine已經(jīng)獲取了鎖,等待隊(duì)列-1
              delta := int32(mutexLocked - 1<<mutexWaiterShift
                   // 當(dāng)前goroutine非饑餓狀態(tài) 或者 等待隊(duì)列只剩下一個(gè)waiter,則退出饑餓模式(清除饑餓標(biāo)識(shí)位)              
              if !starving || old>>mutexWaiterShift == 1 {
               delta -= mutexStarving
              }
                  // 更新?tīng)顟B(tài)值并中止for循環(huán),拿到鎖退出
              atomic.AddInt32(&m.state, delta)
              break
             }
                // 設(shè)置當(dāng)前goroutine為喚醒狀態(tài),且重置自璇次數(shù)
             awoke = true
             iter = 0
            } else {
                // 鎖被其他goroutine占用了,還原狀態(tài)繼續(xù)for循環(huán)
             old = m.state
            }

          這塊的邏輯很復(fù)雜,通過(guò)CAS來(lái)判斷是否獲取到鎖,沒(méi)有通過(guò) CAS 獲得鎖,會(huì)調(diào)用 runtime.sync_runtime_SemacquireMutex通過(guò)信號(hào)量保證資源不會(huì)被兩個(gè) goroutine 獲取,runtime.sync_runtime_SemacquireMutex會(huì)在方法中不斷嘗試獲取鎖并陷入休眠等待信號(hào)量的釋放,一旦當(dāng)前 goroutine 可以獲取信號(hào)量,它就會(huì)立刻返回,如果是新來(lái)的goroutine,就需要放在隊(duì)尾;如果是被喚醒的等待鎖的goroutine,就放在隊(duì)頭,整個(gè)過(guò)程還需要啃代碼來(lái)加深理解。

          解鎖

          相對(duì)于加鎖操作,解鎖的邏輯就沒(méi)有那么復(fù)雜了,接下來(lái)我們來(lái)看一看UnLock的邏輯:

          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)
           }
          }

          使用AddInt32方法快速進(jìn)行解鎖,將m.state的低1位置為0,然后判斷新的m.state值,如果值為0,則代表當(dāng)前鎖已經(jīng)完全空閑了,結(jié)束解鎖,不等于0說(shuō)明當(dāng)前鎖沒(méi)有被占用,會(huì)有等待的goroutine還未被喚醒,需要進(jìn)行一系列喚醒操作,這部分邏輯就在unlockSlow方法內(nèi):

          func (m *Mutex) unlockSlow(new int32) {
            // 這里表示解鎖了一個(gè)沒(méi)有上鎖的鎖,則直接發(fā)生panic
           if (new+mutexLocked)&mutexLocked == 0 {
            throw("sync: unlock of unlocked mutex")
           }
            // 正常模式的釋放鎖邏輯
           if new&mutexStarving == 0 {
            old := new
            for {
                // 如果沒(méi)有等待者則直接返回即可
                // 如果鎖處于加鎖的狀態(tài),表示已經(jīng)有g(shù)oroutine獲取到了鎖,可以返回
                // 如果鎖處于喚醒狀態(tài),這表明有等待的goroutine被喚醒了,不用嘗試獲取其他goroutine了
                // 如果鎖處于饑餓模式,鎖之后會(huì)直接給等待隊(duì)頭goroutine
             if old>>mutexWaiterShift == 0 || old&(mutexLocked|mutexWoken|mutexStarving) != 0 {
              return
             }
             // 搶占喚醒標(biāo)志位,這里是想要把鎖的狀態(tài)設(shè)置為被喚醒,然后waiter隊(duì)列-1
             new = (old - 1<<mutexWaiterShift) | mutexWoken
             if atomic.CompareAndSwapInt32(&m.state, old, new) {
                  // 搶占成功喚醒一個(gè)goroutine
              runtime_Semrelease(&m.sema, false1)
              return
             }
                // 執(zhí)行搶占不成功時(shí)重新更新一下?tīng)顟B(tài)信息,下次for循環(huán)繼續(xù)處理
             old = m.state
            }
           } else {
              // 饑餓模式釋放鎖邏輯,直接喚醒等待隊(duì)列g(shù)oroutine
            runtime_Semrelease(&m.sema, true1)
           }
          }

          我們?cè)趩拘?code style="font-size: 14px;word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: #35b378;">goroutine時(shí)正常模式/饑餓模式都調(diào)用func runtime_Semrelease(s *uint32, handoff bool, skipframes int),這兩種模式在第二個(gè)參數(shù)的傳參上不同,如果handoff is true, pass count directly to the first waiter.

          非阻塞加鎖

          Go語(yǔ)言在1.18版本中引入了非阻塞加鎖的方法TryLock(),其實(shí)現(xiàn)就很簡(jiǎn)潔:

          func (m *Mutex) TryLock() bool {
            // 記錄當(dāng)前狀態(tài)
           old := m.state
            //  處于加鎖狀態(tài)/饑餓狀態(tài)直接獲取鎖失敗
           if old&(mutexLocked|mutexStarving) != 0 {
            return false
           }
           // 嘗試獲取鎖,獲取失敗直接獲取失敗
           if !atomic.CompareAndSwapInt32(&m.state, old, old|mutexLocked) {
            return false
           }


           return true
          }

          TryLock的實(shí)現(xiàn)就比較簡(jiǎn)單了,主要就是兩個(gè)判斷邏輯:

          • 判斷當(dāng)前鎖的狀態(tài),如果鎖處于加鎖狀態(tài)或饑餓狀態(tài)直接獲取鎖失敗
          • 嘗試獲取鎖,獲取失敗直接獲取鎖失敗

          TryLock并不被鼓勵(lì)使用,至少我還沒(méi)想到有什么場(chǎng)景可以使用到它。

          總結(jié)

          通讀源碼后你會(huì)發(fā)現(xiàn)互斥鎖的邏輯真的十分復(fù)雜,代碼量雖然不多,但是很難以理解,一些細(xì)節(jié)點(diǎn)還需要大家多看看幾遍才能理解其為什么這樣做,文末我們?cè)倏偨Y(jié)一下互斥鎖的知識(shí)點(diǎn):

          • 互斥鎖有兩種模式:正常模式、饑餓模式,饑餓模式的出現(xiàn)是為了優(yōu)化正常模式下剛被喚起的goroutine與新創(chuàng)建的goroutine競(jìng)爭(zhēng)時(shí)長(zhǎng)時(shí)間獲取不到鎖,在Go1.9時(shí)引入饑餓模式,如果一個(gè)goroutine獲取鎖失敗超過(guò)1ms,則會(huì)將Mutex切換為饑餓模式,如果一個(gè)goroutine獲得了鎖,并且他在等待隊(duì)列隊(duì)尾 或者 他等待小于1ms,則會(huì)將Mutex的模式切換回正常模式
          • 加鎖的過(guò)程:
            • 鎖處于完全空閑狀態(tài),通過(guò)CAS直接加鎖
            • 當(dāng)鎖處于正常模式、加鎖狀態(tài)下,并且符合自旋條件,則會(huì)嘗試最多4次的自旋
            • 若當(dāng)前goroutine不滿足自旋條件時(shí),計(jì)算當(dāng)前goroutine的鎖期望狀態(tài)
            • 嘗試使用CAS更新鎖狀態(tài),若更新鎖狀態(tài)成功判斷當(dāng)前goroutine是否可以獲取到鎖,獲取到鎖直接退出即可,若獲取不到鎖則陷入睡眠,等待被喚醒
            • goroutine被喚醒后,如果鎖處于饑餓模式,則直接拿到鎖,否則重置自旋次數(shù)、標(biāo)志喚醒位,重新走for循環(huán)自旋、獲取鎖邏輯;
          • 解鎖的過(guò)程
            • 原子操作mutexLocked,如果鎖為完全空閑狀態(tài),直接解鎖成功
            • 如果鎖不是完全空閑狀態(tài),,那么進(jìn)入unlockedslow邏輯
            • 如果解鎖一個(gè)未上鎖的鎖直接panic,因?yàn)闆](méi)加鎖mutexLocked的值為0,解鎖時(shí)進(jìn)行mutexLocked - 1操作,這個(gè)操作會(huì)讓整個(gè)互斥鎖混亂,所以需要有這個(gè)判斷
            • 如果鎖處于饑餓模式直接喚醒等待隊(duì)列隊(duì)頭的waiter
            • 如果鎖處于正常模式下,沒(méi)有等待的goroutine可以直接退出,如果鎖已經(jīng)處于鎖定狀態(tài)、喚醒狀態(tài)、饑餓模式則可以直接退出,因?yàn)橐呀?jīng)有被喚醒的 goroutine 獲得了鎖.
          • 使用互斥鎖時(shí)切記拷貝Mutex,因?yàn)榭截?code style="font-size: 14px;word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: #35b378;">Mutex時(shí)會(huì)連帶狀態(tài)一起拷貝,因?yàn)?code style="font-size: 14px;word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: #35b378;">Lock時(shí)只有鎖在完全空閑時(shí)才會(huì)獲取鎖成功,拷貝時(shí)連帶狀態(tài)一起拷貝后,會(huì)造成死鎖
          • TryLock的實(shí)現(xiàn)邏輯很簡(jiǎn)單,主要判斷當(dāng)前鎖處于加鎖狀態(tài)、饑餓模式就會(huì)直接獲取鎖失敗,嘗試獲取鎖失敗直接返回;

          本文之后你對(duì)互斥鎖有什么不理解的嗎?歡迎評(píng)論區(qū)批評(píng)指正~;

          好啦,本文到這里就結(jié)束了。


          瀏覽 52
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  91射在线播放 | 在线观看日本黄 | 成人青娱乐| 丁香五月婷婷综合小说 | 成人在线免费观看黄片 |