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

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

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

鎖底層

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

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

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

mutex

提供了三個方法

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

實現(xiàn)如下

type Mutex struct {
    state int32
    sema  uint32
}

Mutex只有兩個字段

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

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

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

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

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

加鎖

func (m *Mutex) Lock() {
    // 判斷當前鎖的狀態(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)用判斷當前鎖的狀態(tài),如果是空閑則m.state為0,這個時候?qū)ζ浼渔i,將m.state設為1
  • 如果當前鎖已被占用,通過lockSlow方法嘗試自旋或者饑餓狀態(tài)下的競爭,等待鎖的釋放

lockSlow:

初始化五個字段

  • waitStartTime 用來計算waiter的等待時間
  • starving 饑餓模式標志,如果等待時間超過1ms,則為true
  • awoke 協(xié)程是否喚醒,當g在自旋的時候,相當于CPU上已經(jīng)有正在等鎖的協(xié)程,為了避免mutex解鎖時再喚醒其他協(xié)程,自旋時要嘗試把mutex設為喚醒狀態(tài)
  • iter 用來記錄協(xié)程的自旋次數(shù)
  • old 記錄當前鎖的狀態(tài)

判斷自旋

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

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

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

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

解鎖

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

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

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

非阻塞加鎖

func (m *Mutex) TryLock() bool {
  // 記錄當前狀態(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新加入的方法,不被鼓勵使用,主要是兩個判斷邏輯

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

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

相關文章

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

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

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

    Go日志框架zap增強及源碼解讀

    這篇文章主要為大家介紹了Go日志框架zap增強及源碼解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    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實現(xiàn)CRUD的實戰(zhàn)

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

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

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

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

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

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

    Go語言中sync.Cond使用詳解

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

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

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

    go中的protobuf和grpc使用教程

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

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

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

最新評論