亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

一文帶你了解Go語言中鎖特性和實(shí)現(xiàn)

 更新時(shí)間:2024年03月07日 09:20:46   作者:安妮的心動錄  
Go語言中的sync包主要提供的對并發(fā)操作的支持,標(biāo)志性的工具有cond(條件變量)?once?(原子性)?還有?鎖,本文會主要向大家介紹Go語言中鎖的特性和實(shí)現(xiàn),感興趣的可以了解下

鎖底層

go中的sync包提供了兩種鎖的類型,分別是互斥鎖sync.Mutex和讀寫鎖sync.RWMutex,這兩種鎖都屬于悲觀鎖

鎖的使用場景是解決多協(xié)程下數(shù)據(jù)競態(tài)的問題,為了保證數(shù)據(jù)的安全,鎖住一些共享資源。以防止并發(fā)訪問這些共享數(shù)據(jù)時(shí)可能導(dǎo)致的數(shù)據(jù)不一致問題,獲取鎖的線程可以正常訪問臨界區(qū),未獲取到鎖的線程等待鎖釋放之后可以嘗試獲取鎖

注:當(dāng)你想讓一個(gè)結(jié)構(gòu)體是并發(fā)安全的,可以加一個(gè)鎖字段,比如channel就是這么做的,要注意的是,這個(gè)鎖字段必須小寫,不然調(diào)用方也可以進(jìn)行l(wèi)ock和unlock操作,相當(dāng)于你把鑰匙和鎖都交給了別人,鎖就失去了應(yīng)有的作用

mutex

提供了三個(gè)方法

  • Lock() 進(jìn)行加鎖操作,在同一個(gè)goroutine中必須在鎖釋放之后才能進(jìn)行再次上鎖,不然會panic
  • Unlock() 進(jìn)行解鎖操作,如果這個(gè)時(shí)候未加鎖會panic,mutex和goroutine不關(guān)聯(lián),也就是說對于mutex的加鎖解鎖操作可以發(fā)生在多個(gè)goroutine間
  • tryLock() 嘗試獲取鎖,當(dāng)鎖被其他goroutine占有,或者鎖處于饑餓模式,將立刻返回false,當(dāng)鎖可用時(shí)嘗試獲取鎖,獲取失敗也返回false

實(shí)現(xiàn)如下

type Mutex struct {
    state int32
    sema  uint32
}

Mutex只有兩個(gè)字段

  • state 表示當(dāng)前互斥鎖的狀態(tài),復(fù)合型字段
  • sema 信號量變量,用來控制等待goroutine的阻塞休眠和喚醒

state的不同位標(biāo)識了不同的狀態(tài),以此實(shí)現(xiàn)了用最小的內(nèi)存來表示更多的意義

// 前三個(gè)字段標(biāo)識了鎖的狀態(tài)  剩下的位來標(biāo)識當(dāng)前共有多少個(gè)goroutine在等待鎖
const (
   mutexLocked = 1 << iota // 表示互斥鎖的鎖定狀態(tài)
   mutexWoken // 表示從正常模式被從喚醒
   mutexStarving // 當(dāng)前的互斥鎖進(jìn)入饑餓狀態(tài)
   mutexWaiterShift = iota // 當(dāng)前互斥鎖上等待者的數(shù)量
)

mutex的最開始實(shí)現(xiàn)只有正常模式,在正常模式下等待的線程按照先進(jìn)先出的方式獲取鎖,但是新創(chuàng)建的goroutine會與剛被喚醒的goroutine競爭,導(dǎo)致剛被喚起的goroutine拿不到鎖,從而長期被阻塞。

因此Go在1.9版本中引入了饑餓模式,當(dāng)goroutine超過1ms沒有獲取鎖,那么就將當(dāng)前的互斥鎖切換到饑餓模式,在該模式下,互斥鎖會直接交給等待隊(duì)列最前面的g,新的g在該狀態(tài)下既不能獲取鎖,也不會進(jìn)入自旋狀態(tài),只會在隊(duì)列的末尾等待。如果一個(gè)g獲取了互斥鎖,并且它在隊(duì)列的末尾或者等待的時(shí)間少于1ms,那么就回到正常模式

加鎖

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

func (m *Mutex) lockSlow() {
    var waitStartTime int64 
    starving := false
    awoke := false
    iter := 0
    old := m.state
    ........
}
  • 通過CAS系統(tǒng)調(diào)用判斷當(dāng)前鎖的狀態(tài),如果是空閑則m.state為0,這個(gè)時(shí)候?qū)ζ浼渔i,將m.state設(shè)為1
  • 如果當(dāng)前鎖已被占用,通過lockSlow方法嘗試自旋或者饑餓狀態(tài)下的競爭,等待鎖的釋放

lockSlow:

初始化五個(gè)字段

  • waitStartTime 用來計(jì)算waiter的等待時(shí)間
  • starving 饑餓模式標(biāo)志,如果等待時(shí)間超過1ms,則為true
  • awoke 協(xié)程是否喚醒,當(dāng)g在自旋的時(shí)候,相當(dāng)于CPU上已經(jīng)有正在等鎖的協(xié)程,為了避免mutex解鎖時(shí)再喚醒其他協(xié)程,自旋時(shí)要嘗試把mutex設(shè)為喚醒狀態(tài)
  • 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 表示沒有其他正在喚醒的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
        }
}

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

進(jìn)入自旋的原因:樂觀的認(rèn)為當(dāng)前正在持有鎖的g能在短時(shí)間內(nèi)歸還鎖,所以需要一些條件來判斷:到底能不能短時(shí)間歸還
條件如下

  • 自旋的次數(shù)<=4
  • cpu必須為多核
  • gomaxprocs>1,最大被同時(shí)執(zhí)行的CPU數(shù)目大于1
  • 當(dāng)前機(jī)器上至少存在一個(gè)正在運(yùn)行的P并且處理隊(duì)列為空

滿足條件之后進(jìn)行循環(huán),次數(shù)為30次,也就是執(zhí)行30次PAUSE指令來占據(jù)CPU,進(jìn)行自旋

解鎖

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

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

解鎖對于加鎖來說簡單很多,通過AddInt32方法進(jìn)行快速解鎖,將m.state低位置為0,然后判斷值,如果為0,那么就完全空閑了,結(jié)束解鎖。如果不為0說明當(dāng)前鎖未被占用,不過有等待的g未被喚醒,需要進(jìn)行一系列喚醒操作,喚醒判斷鎖的狀態(tài),然后進(jìn)行具體的goroutine喚醒

非阻塞加鎖

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是Go 1.18新加入的方法,不被鼓勵使用,主要是兩個(gè)判斷邏輯

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

以上就是一文帶你了解Go語言中鎖特性和實(shí)現(xiàn)的詳細(xì)內(nèi)容,更多關(guān)于Go鎖的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • golang框架gin的日志處理和zap lumberjack日志使用方式

    golang框架gin的日志處理和zap lumberjack日志使用方式

    這篇文章主要介紹了golang框架gin的日志處理和zap lumberjack日志使用方式,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-01-01
  • Go日志框架zap增強(qiáng)及源碼解讀

    Go日志框架zap增強(qiáng)及源碼解讀

    這篇文章主要為大家介紹了Go日志框架zap增強(qiáng)及源碼解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-07-07
  • 從淺入深帶你掌握Golang數(shù)據(jù)結(jié)構(gòu)map

    從淺入深帶你掌握Golang數(shù)據(jù)結(jié)構(gòu)map

    在?Go?語言中,map?是一種非常常見的數(shù)據(jù)類型,它可以用于快速地檢索數(shù)據(jù)。本篇文章將介紹?Go?語言中的?map,包括?map?的定義、初始化、操作和優(yōu)化,需要的可以參考一下
    2023-04-04
  • Gin+Gorm實(shí)現(xiàn)CRUD的實(shí)戰(zhàn)

    Gin+Gorm實(shí)現(xiàn)CRUD的實(shí)戰(zhàn)

    本文主要介紹了Gin+Gorm實(shí)現(xiàn)CRUD的實(shí)戰(zhàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-02-02
  • 超實(shí)用的Golang通道指南之輕松實(shí)現(xiàn)并發(fā)編程

    超實(shí)用的Golang通道指南之輕松實(shí)現(xiàn)并發(fā)編程

    Golang?中的通道是一種高效、安全、靈活的并發(fā)機(jī)制,用于在并發(fā)環(huán)境下實(shí)現(xiàn)數(shù)據(jù)的同步和傳遞。本文主要介紹了如何利用通道輕松實(shí)現(xiàn)并發(fā)編程,需要的可以參考一下
    2023-04-04
  • go中for?range的坑以及解決方案

    go中for?range的坑以及解決方案

    相信小伙伴都遇到過以下的循環(huán)變量的問題,本文主要介紹了go中for?range的坑以及解決方案,具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-01-01
  • Go語言中sync.Cond使用詳解

    Go語言中sync.Cond使用詳解

    本文主要介紹了Go語言中sync.Cond使用詳解,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-09-09
  • Golang實(shí)現(xiàn)程序優(yōu)雅退出的方法詳解

    Golang實(shí)現(xiàn)程序優(yōu)雅退出的方法詳解

    項(xiàng)目開發(fā)過程中,隨著需求的迭代,代碼的發(fā)布會頻繁進(jìn)行,在發(fā)布過程中,Golang如何讓程序做到優(yōu)雅的退出?本文就來詳細(xì)為大家講講
    2022-06-06
  • go中的protobuf和grpc使用教程

    go中的protobuf和grpc使用教程

    gRPC 是 Google 公司基于 Protobuf 開發(fā)的跨語言的開源 RPC 框架,這篇文章主要介紹了go中的protobuf和grpc使用教程,需要的朋友可以參考下
    2024-08-08
  • Golang 操作TSV文件的實(shí)戰(zhàn)示例

    Golang 操作TSV文件的實(shí)戰(zhàn)示例

    本文主要介紹了Golang 操作TSV文件的實(shí)戰(zhàn)示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-03-03

最新評論