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

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

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

mutex 初步認(rèn)識(shí)

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

type Mutex struct {
    state int32
    sema  uint32
}

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

PV 原語解釋:

通過操作信號(hào)量 S 來處理進(jìn)程間的同步與互斥的問題。

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

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

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

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

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

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

當(dāng) mutex 釋放鎖資源時(shí),則會(huì)繼續(xù)喚起之前的 goroutine 去搶占鎖資源。

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

mutex 狀態(tài)標(biāo)志位

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

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

mutex 正常模式

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

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

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

mutex 饑餓模式

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

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

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

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

mutex 自旋

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

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

自旋的條件如下:

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

可以看出,自旋條件還是比較嚴(yán)格的,畢竟這會(huì)消耗 CPU 的運(yùn)算能力。

mutex 的 Lock() 過程

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

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

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

當(dāng)有鎖資源釋放,mutex 在喚起了隊(duì)頭的 goroutine 后,隊(duì)頭 goroutine 會(huì)嘗試性的占有鎖資源,而此時(shí)也有可能會(huì)和新到來的 goroutine 一起競(jìng)爭(zhēng)。

當(dāng)隊(duì)頭 goroutine 一直得不到資源時(shí),則會(huì)進(jìn)入饑餓模式,直接將鎖資源交給隊(duì)頭 goroutine,讓新來的 goroutine 阻塞并加入到等待隊(duì)列的隊(duì)尾里。

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

Unlock 過程

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

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

mutex 代碼詳解

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

mutex Unlock() 代碼詳解

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

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

相關(guān)文章

  • golang?struct?json?tag的使用以及深入講解

    golang?struct?json?tag的使用以及深入講解

    這篇文章主要給大家介紹了關(guān)于golang?struct?json?tag的使用以及深入講解,文中通過實(shí)例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2022-02-02
  • golang panic及處理機(jī)制

    golang panic及處理機(jī)制

    Go語言追求簡(jiǎn)潔優(yōu)雅,所以,Go語言不支持傳統(tǒng)的 try…catch…finally 這種異常,因?yàn)镚o語言的設(shè)計(jì)者們認(rèn)為,將異常與控制結(jié)構(gòu)混在一起會(huì)很容易使得代碼變得混亂,今天給大家介紹golang panic及處理機(jī)制,需要的朋友參考下吧
    2021-08-08
  • Go+Kafka實(shí)現(xiàn)延遲消息的實(shí)現(xiàn)示例

    Go+Kafka實(shí)現(xiàn)延遲消息的實(shí)現(xiàn)示例

    本文主要介紹了Go+Kafka實(shí)現(xiàn)延遲消息的實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-07-07
  • Go Java算法之Excel表列名稱示例詳解

    Go Java算法之Excel表列名稱示例詳解

    這篇文章主要為大家介紹了Go Java算法之Excel表列名稱示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-08-08
  • Golang Printf,Sprintf,Fprintf 格式化詳解

    Golang Printf,Sprintf,Fprintf 格式化詳解

    這篇文章主要介紹了Golang Printf,Sprintf,Fprintf 格式化詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2021-03-03
  • Go設(shè)計(jì)模式之迭代器模式講解和代碼示例

    Go設(shè)計(jì)模式之迭代器模式講解和代碼示例

    迭代器是一種行為設(shè)計(jì)模式, 讓你能在不暴露復(fù)雜數(shù)據(jù)結(jié)構(gòu)內(nèi)部細(xì)節(jié)的情況下遍歷其中所有的元素,本文將為大家詳細(xì)介紹Go 迭代器模式,文中詳細(xì)的代碼示例,需要的朋友可以參考下
    2023-07-07
  • 使用go進(jìn)行云存儲(chǔ)上傳實(shí)現(xiàn)實(shí)例

    使用go進(jìn)行云存儲(chǔ)上傳實(shí)現(xiàn)實(shí)例

    這篇文章主要為大家介紹了使用go進(jìn)行云存儲(chǔ)上傳實(shí)例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪<BR>
    2024-01-01
  • Go中使用gjson來操作JSON數(shù)據(jù)的實(shí)現(xiàn)

    Go中使用gjson來操作JSON數(shù)據(jù)的實(shí)現(xiàn)

    本文主要介紹了Go中使用gjson來操作JSON數(shù)據(jù),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-08-08
  • Golang?分割字符串的實(shí)現(xiàn)示例

    Golang?分割字符串的實(shí)現(xiàn)示例

    本文主要介紹了Golang?分割字符串的實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-04-04
  • go語言中GoMock安裝使用詳解

    go語言中GoMock安裝使用詳解

    這篇文章主要為大家介紹了go語言中GoMock安裝使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-11-11

最新評(píng)論

洛宁县| 行唐县| 莱州市| 齐齐哈尔市| 钦州市| 崇阳县| 富阳市| 怀远县| 平湖市| 焦作市| 吉林省| 拜城县| 北安市| 徐水县| 新竹县| 二连浩特市| 阳东县| 舞阳县| 海晏县| 海晏县| 泸水县| 额尔古纳市| 三江| 沂南县| 土默特右旗| 汝城县| 金沙县| 南通市| 寿宁县| 桐乡市| 包头市| 双桥区| 万安县| 天等县| 怀来县| 城步| 湖州市| 琼海市| 那坡县| 沙湾县| 宁强县|