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

go mutex互斥鎖使用Lock和Unlock方法占有釋放資源

 更新時間:2023年09月27日 09:17:37   作者:lincoln_hlf1  
Go號稱是為了高并發(fā)而生的,在高并發(fā)場景下,勢必會涉及到對公共資源的競爭,當對應(yīng)場景發(fā)生時,我們經(jīng)常會使用 mutex 的 Lock() 和 Unlock() 方法來占有或釋放資源,雖然調(diào)用簡單,但 mutex 的內(nèi)部卻涉及挺多的,本文來好好研究一下

mutex 初步認識

mutex 的源碼主要是在 src/sync/mutex.go文件里,它的結(jié)構(gòu)體比較簡單,如下:

type Mutex struct {
    state int32
    sema  uint32
}

我們可以看到有一個字段 sema,它表示信號量標記位。所謂的信號量是用于 Goroutine 之間阻塞或喚醒的。這有點像操作系統(tǒng)里的 PV 原語操作,我們先來認識下 PV 原語操作:

PV 原語解釋:

通過操作信號量 S 來處理進程間的同步與互斥的問題。

S>0:表示有 S 個資源可用;S=0 表示無資源可用;S<0 絕對值表示等待隊列或鏈表中的進程個數(shù)。信號量 S 的初值應(yīng)大于等于 0。

P 原語:表示申請一個資源,對 S 原子性的減 1,若 減 1 后仍 S>=0,則該進程繼續(xù)執(zhí)行;若 減 1 后 S<0,表示已無資源可用,需要將自己阻塞起來,放到等待隊列上。

V 原語:表示釋放一個資源,對 S 原子性的加 1;若 加 1 后 S>0,則該進程繼續(xù)執(zhí)行;若 加 1 后 S<=0,表示等待隊列上有等待進程,需要將第一個等待的進程喚醒。

通過上面的解釋,mutex 就可以利用信號量來實現(xiàn) goroutine 的阻塞和喚起了。

其實 mutex 本質(zhì)上就是一個關(guān)于信號量阻塞喚起操作。

當 goroutine 不能占有鎖資源的時候會被阻塞掛起,此時不能繼續(xù)執(zhí)行后面的代碼邏輯。

當 mutex 釋放鎖資源時,則會繼續(xù)喚起之前的 goroutine 去搶占鎖資源。

至于 mutex 的 state 狀態(tài)字段則是用來做狀態(tài)流轉(zhuǎn)的,這些狀態(tài)值涉及到了一些概念,下面我們具體來解釋一番。

mutex 狀態(tài)標志位

mutex 的 state 有 32 位,它的低 3 位分別表示 3 種狀態(tài):喚醒狀態(tài)、上鎖狀態(tài)、饑餓狀態(tài),剩下的位數(shù)則表示當前阻塞等待的 goroutine 數(shù)量。

mutex 會根據(jù)當前的 state 狀態(tài)來進入正常模式、饑餓模式或者是自旋。

mutex 正常模式

當 mutex 調(diào)用 Unlock() 方法釋放鎖資源時,如果發(fā)現(xiàn)有等待喚起的 Goroutine 隊列時,則會將隊頭的 Goroutine 喚起。

隊頭的 goroutine 被喚起后,會調(diào)用 CAS 方法去嘗試性的修改 state 狀態(tài),如果修改成功,則表示占有鎖資源成功。

(注:CAS 在 Go 里用 atomic.CompareAndSwapInt32(addr *int32, old, new int32) 方法實現(xiàn),CAS 類似于樂觀鎖作用,修改前會先判斷地址值是否還是 old 值,只有還是 old 值,才會繼續(xù)修改成 new 值,否則會返回 false 表示修改失敗。)

mutex 饑餓模式

由于上面的 Goroutine 喚起后并不是直接的占用資源,還需要調(diào)用 CAS 方法去嘗試性占有鎖資源。如果此時有新來的 Goroutine,那么它也會調(diào)用 CAS 方法去嘗試性的占有資源。

但對于 Go 的調(diào)度機制來講,會比較偏向于 CPU 占有時間較短的 Goroutine 先運行,而這將造成一定的幾率讓新來的 Goroutine 一直獲取到鎖資源,此時隊頭的 Goroutine 將一直占用不到,導致餓死。

針對這種情況,Go 采用了饑餓模式。即通過判斷隊頭 Goroutine 在超過一定時間后還是得不到資源時,會在 Unlock 釋放鎖資源時,直接將鎖資源交給隊頭 Goroutine,并且將當前狀態(tài)改為饑餓模式

后面如果有新來的 Goroutine 發(fā)現(xiàn)是饑餓模式時, 則會直接添加到等待隊列的隊尾。

mutex 自旋

如果 Goroutine 占用鎖資源的時間比較短,那么每次都調(diào)用信號量來阻塞喚起 goroutine,將會很浪費資源。

因此在符合一定條件后,mutex 會讓當前的 Goroutine 去空轉(zhuǎn) CPU,在空轉(zhuǎn)完后再次調(diào)用 CAS 方法去嘗試性的占有鎖資源,直到不滿足自旋條件,則最終會加入到等待隊列里。

自旋的條件如下:

  • 還沒自旋超過 4 次
  • 多核處理器
  • GOMAXPROCS > 1
  • p 上本地 Goroutine 隊列為空

可以看出,自旋條件還是比較嚴格的,畢竟這會消耗 CPU 的運算能力。

mutex 的 Lock() 過程

首先,如果 mutex 的 state = 0,即沒有誰在占有資源,也沒有阻塞等待喚起的 goroutine。則會調(diào)用 CAS 方法去嘗試性占有鎖,不做其他動作。

如果不符合 m.state = 0,則進一步判斷是否需要自旋。

當不需要自旋又或者自旋后還是得不到資源時,此時會調(diào)用 runtime_SemacquireMutex 信號量函數(shù),將當前的 goroutine 阻塞并加入等待喚起隊列里。

當有鎖資源釋放,mutex 在喚起了隊頭的 goroutine 后,隊頭 goroutine 會嘗試性的占有鎖資源,而此時也有可能會和新到來的 goroutine 一起競爭。

當隊頭 goroutine 一直得不到資源時,則會進入饑餓模式,直接將鎖資源交給隊頭 goroutine,讓新來的 goroutine 阻塞并加入到等待隊列的隊尾里。

對于饑餓模式將會持續(xù)到?jīng)]有阻塞等待喚起的 goroutine 隊列時,才會解除。

Unlock 過程

mutex 的 Unlock() 則相對簡單。同樣的,會先進行快速的解鎖,即沒有等待喚起的 goroutine,則不需要繼續(xù)做其他動作。

如果當前是正常模式,則簡單的喚起隊頭 Goroutine。如果是饑餓模式,則會直接將鎖交給隊頭 Goroutine,然后喚起隊頭 Goroutine,讓它繼續(xù)運行。

mutex 代碼詳解

好了,上面大體流程講完了,下面將會把詳細的代碼流程呈上,讓大家能更詳細的知道 mutex 的 Lock()、Unlock() 方法邏輯。

mutex Lock() 代碼詳解

// Lock mutex 的鎖方法。
func (m *Mutex) Lock() {
    // 快速上鎖.
    if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {
        if race.Enabled {
            race.Acquire(unsafe.Pointer(m))
        }
        return
    }
    // 快速上鎖失敗,將進行操作較多的上鎖動作。
    m.lockSlow()
}
func (m *Mutex) lockSlow() {
  var waitStartTime int64  // 記錄當前 goroutine 的等待時間
  starving := false // 是否饑餓
  awoke := false // 是否被喚醒
  iter := 0 // 自旋次數(shù)
  old := m.state // 當前 mutex 的狀態(tài)
  for {
    // 當前 mutex 的狀態(tài)已上鎖,并且非饑餓模式,并且符合自旋條件
    if old&(mutexLocked|mutexStarving) == mutexLocked && runtime_canSpin(iter) {
      // 當前還沒設(shè)置過喚醒標識
      if !awoke && old&mutexWoken == 0 && old>>mutexWaiterShift != 0 &&
        atomic.CompareAndSwapInt32(&m.state, old, old|mutexWoken) {
        awoke = true
      }
      runtime_doSpin()
      iter++
      old = m.state
      continue
    }
    new := old
    // 如果不是饑餓狀態(tài),則嘗試上鎖
    // 如果是饑餓狀態(tài),則不會上鎖,因為當前的 goroutine 將會被阻塞并添加到等待喚起隊列的隊尾
    if old&mutexStarving == 0 {
      new |= mutexLocked
    }
    // 等待隊列數(shù)量 + 1
    if old&(mutexLocked|mutexStarving) != 0 {
      new += 1 << mutexWaiterShift
    }
    // 如果 goroutine 之前是饑餓模式,則此次也設(shè)置為饑餓模式
    if starving && old&mutexLocked != 0 {
      new |= mutexStarving
    }
    //
    if awoke {
      // 如果狀態(tài)不符合預(yù)期,則報錯
      if new&mutexWoken == 0 {
        throw("sync: inconsistent mutex state")
      }
      // 新狀態(tài)值需要清除喚醒標識,因為當前 goroutine 將會上鎖或者再次 sleep
      new &^= mutexWoken
    }
    // CAS 嘗試性修改狀態(tài),修改成功則表示獲取到鎖資源
    if atomic.CompareAndSwapInt32(&m.state, old, new) {
      // 非饑餓模式,并且未獲取過鎖,則說明此次的獲取鎖是 ok 的,直接 return
      if old&(mutexLocked|mutexStarving) == 0 {
        break
      }
      // 根據(jù)等待時間計算 queueLifo
      queueLifo := waitStartTime != 0
      if waitStartTime == 0 {
        waitStartTime = runtime_nanotime()
      }
      // 到這里,表示未能上鎖成功
      // queueLife = true, 將會把 goroutine 放到等待隊列隊頭
      // queueLife = false, 將會把 goroutine 放到等待隊列隊尾
      runtime_SemacquireMutex(&m.sema, queueLifo, 1)
      // 計算是否符合饑餓模式,即等待時間是否超過一定的時間
      starving = starving || runtime_nanotime()-waitStartTime > starvationThresholdNs
      old = m.state
      // 上一次是饑餓模式
      if old&mutexStarving != 0 {
        if old&(mutexLocked|mutexWoken) != 0 || old>>mutexWaiterShift == 0 {
          throw("sync: inconsistent mutex state")
        }
        delta := int32(mutexLocked - 1<<mutexWaiterShift)
        // 此次不是饑餓模式又或者下次沒有要喚起等待隊列的 goroutine 了
        if !starving || old>>mutexWaiterShift == 1 {
          delta -= mutexStarving
        }
        atomic.AddInt32(&m.state, delta)
        break
      }
      // 此處已不再是饑餓模式了,清除自旋次數(shù),重新到 for 循環(huán)競爭鎖。
      awoke = true
      iter = 0
    } else {
      old = m.state
    }
  }
  if race.Enabled {
    race.Acquire(unsafe.Pointer(m))
  }
}

mutex Unlock() 代碼詳解

// Unlock 對 mutex 解鎖.
// 如果沒有上過鎖,缺調(diào)用此方法解鎖,將會拋出運行時錯誤。
// 它將允許在不同的 Goroutine 上進行上鎖解鎖
func (m *Mutex) Unlock() {
    if race.Enabled {
        _ = m.state
        race.Release(unsafe.Pointer(m))
    }
    // 快速嘗試解鎖
    new := atomic.AddInt32(&m.state, -mutexLocked)
    if new != 0 {
        // 快速解鎖失敗,將進行操作較多的解鎖動作。
        m.unlockSlow(new)
    }
}
func (m *Mutex) unlockSlow(new int32) {
  // 非上鎖狀態(tài),直接拋出異常
  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
      }
      // 喚起等待隊列并數(shù)量-1
      new = (old - 1<<mutexWaiterShift) | mutexWoken
      if atomic.CompareAndSwapInt32(&m.state, old, new) {
        runtime_Semrelease(&m.sema, false, 1)
        return
      }
      old = m.state
    }
  } else {
    //饑餓模式,將鎖直接給等待隊列的隊頭 goroutine
    runtime_Semrelease(&m.sema, true, 1)
  }
}

以上就是go mutex互斥鎖使用Lock和Unlock方法占有釋放資源的詳細內(nèi)容,更多關(guān)于golang mutex互斥鎖的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • GO使用socket和channel實現(xiàn)簡單控制臺聊天室

    GO使用socket和channel實現(xiàn)簡單控制臺聊天室

    今天小編給大家分享一個簡單的聊天室功能,聊天室主要功能是用戶可以加入離開聊天室,實現(xiàn)思路也很簡單明了,下面小編給大家?guī)砹送暾a,感興趣的朋友跟隨小編一起看看吧
    2021-12-12
  • go語言中for?range使用方法及避坑指南

    go語言中for?range使用方法及避坑指南

    Go中的for range組合可以和方便的實現(xiàn)對一個數(shù)組或切片進行遍歷,但是在某些情況下使用for range時很可能就會被"坑",下面這篇文章主要給大家介紹了關(guān)于go語言中for?range使用方法及避坑指南的相關(guān)資料,需要的朋友可以參考下
    2022-09-09
  • golang 實現(xiàn)tcp轉(zhuǎn)發(fā)代理的方法

    golang 實現(xiàn)tcp轉(zhuǎn)發(fā)代理的方法

    今天小編就為大家分享一篇golang 實現(xiàn)tcp轉(zhuǎn)發(fā)代理的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2019-08-08
  • Go net/http/pprof分析內(nèi)存泄露及解決過程

    Go net/http/pprof分析內(nèi)存泄露及解決過程

    這篇文章主要介紹了Go net/http/pprof分析內(nèi)存泄露及解決過程,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2025-04-04
  • Go語言基礎(chǔ)枚舉的用法及示例詳解

    Go語言基礎(chǔ)枚舉的用法及示例詳解

    這篇文章主要為大家介紹了Go語言基礎(chǔ)枚舉的用法及示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2021-11-11
  • Go實現(xiàn)并發(fā)緩存的示例代碼

    Go實現(xiàn)并發(fā)緩存的示例代碼

    高并發(fā)數(shù)據(jù)存儲是現(xiàn)代互聯(lián)網(wǎng)應(yīng)用開發(fā)中常遇到的一大挑戰(zhàn),本文主要介紹了Go實現(xiàn)并發(fā)緩存的示例代碼,具有一定的參考價值,感興趣的可以了解一下
    2023-10-10
  • Go中RPC遠程過程調(diào)用的實現(xiàn)

    Go中RPC遠程過程調(diào)用的實現(xiàn)

    本文主要介紹了Go中RPC遠程過程調(diào)用的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2023-07-07
  • goland中導包報紅和go mod問題

    goland中導包報紅和go mod問題

    這篇文章主要介紹了goland中導包報紅和go mod問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-03-03
  • Go 1.21新增的slices包中切片函數(shù)用法詳解

    Go 1.21新增的slices包中切片函數(shù)用法詳解

    Go 1.21新增的 slices 包提供了很多和切片相關(guān)的函數(shù),可以用于任何類型的切片,本文通過代碼示例為大家介紹了部分切片函數(shù)的具體用法,感興趣的小伙伴可以了解一下
    2023-08-08
  • GO語言中回調(diào)函數(shù)的使用

    GO語言中回調(diào)函數(shù)的使用

    本文主要介紹了GO語言中回調(diào)函數(shù)的使用,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2023-03-03

最新評論