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

Go語言sync鎖與對象池的實現(xiàn)

 更新時間:2025年08月21日 10:32:13   作者:onlooker6666  
本文介紹了Go語言標準庫sync包提供的并發(fā)控制工具,主要包括互斥鎖(sync.Mutex)和讀寫鎖(sync.RWMutex)兩類同步機制,下面就來具體介紹一下這兩個的使用,感興趣的可以了解一下

1. 背景

在并發(fā)編程中,正確地管理共享資源是構(gòu)建高性能程序的關(guān)鍵。Go 語言標準庫中的 sync 包提供了一組基礎(chǔ)而強大的并發(fā)原語,用于實現(xiàn)安全的協(xié)程間同步與資源控制。本文將簡要介紹 sync 包中常用的類型和方法: sync 鎖 與 對象池,幫助開發(fā)者更高效地編寫并發(fā)安全的 Go 程序。

2. 鎖

go語言是出了名的高并發(fā)利器 , 但在高并發(fā)場景下 , 伴隨而來的數(shù)據(jù)安全問題是需要解決的。 加鎖就是其中的一個解決辦法。

多個線程同時訪問臨界區(qū),鎖住一些共享資源, 以防止并發(fā)訪問這些共享數(shù)據(jù)時可能導(dǎo)致的數(shù)據(jù)不一致問題

獲取鎖的線程可以正常訪問臨界區(qū),未獲取到鎖的線程等待鎖釋放后可以嘗試獲取鎖。

sync.Locker 是 go 標準庫 sync 下定義的鎖接口:

// A Locker represents an object that can be locked and unlocked.
type Locker interface {
    Lock()
    Unlock()
}

任何實現(xiàn)了 Lock 和 Unlock 兩個方法的類,都可以作為一種鎖的實現(xiàn)。
Go 語言包中的 sync 包提供了兩種鎖類型:sync.Mutex 和 sync.RWMutex,前者是互斥鎖,后者是讀寫鎖。

2.1 互斥鎖 (sync.Mutex)

互斥即不可同時運行。即使用了互斥鎖的兩個代碼片段互相排斥,只有其中一個代碼片段執(zhí)行完成后,另一個才能執(zhí)行。
Go 標準庫中提供了 sync.Mutex 互斥鎖類型及其兩個方法:

  • Lock 加鎖
    使用 Lock () 加鎖后,該線程不能再繼續(xù)對其加鎖,否則會 panic。只有在 unlock () 之后才能再次 Lock ()。異步調(diào)用 Lock (),是正當(dāng)?shù)逆i競爭,當(dāng)然不會有 panic 了。適用于讀寫不確定場景,即讀寫次數(shù)沒有明顯的區(qū)別,并且只允許只有一個讀或者寫的場景,所以該鎖也叫做全局鎖。

  • Unlock 釋放鎖
    Unlock () 用于解鎖 m,如果在使用 Unlock () 前未加鎖,就會引起一個運行錯誤。已經(jīng)鎖定的 Mutex 并不與特定的 goroutine 相關(guān)聯(lián),這樣可以利用一個 goroutine 對其加鎖,再利用其他 goroutine 對其解鎖。

2.1.1 使用方法

var lck sync.Mutex
func foo() {
    lck.Lock() 
    defer lck.Unlock()
    // ...
}

2.2 讀寫鎖 (sync.RWMutex)

讀寫鎖是分別針對讀操作和寫操作進行鎖定和解鎖操作的互斥鎖。
RWMutex 提供四個方法:

func (*RWMutex) Lock // 寫鎖定
func (*RWMutex) Unlock // 寫解鎖
func (*RWMutex) RLock // 讀鎖定
func (*RWMutex) RUnlock // 讀解鎖
  • 寫鎖定情況下,對讀寫鎖進行讀鎖定或者寫鎖定,都將阻塞;而且讀鎖與寫鎖之間是互斥的;
  • 讀鎖定情況下,對讀寫鎖進行寫鎖定,將阻塞;加讀鎖時不會阻塞
操作1 \ 操作2RLock()(讀鎖)Lock()(寫鎖)RUnlock()Unlock()
RLock()? 并發(fā)允許? 阻塞等待寫鎖釋放? 無影響? 無影響
Lock()? 阻塞等待讀鎖釋放? 阻塞等待寫鎖釋放? 無影響? 無影響
RUnlock()? 無影響? 無影響? 無影響? 無影響
Unlock()? 無影響? 無影響? 無影響? 無影響

讀寫鎖的存在是為了解決讀多寫少時的性能問題,讀場景較多時,讀寫鎖可有效地減少鎖阻塞的時間。

3. 對象池 (sync.Pool)

sync.Pool 的使用場景 : 保存和復(fù)用臨時對象,減少內(nèi)存分配,降低 GC 壓力。
舉例 : Gin 框架中的 context 包每次面對接口調(diào)用時都需要創(chuàng)建 ,貫穿整個調(diào)用鏈路。底層就使用對象池進行優(yōu)化。

sync.Pool 是可伸縮的,同時也是并發(fā)安全的,其大小僅受限于內(nèi)存的大小。sync.Pool 用于存儲那些被分配了但是沒有被使用,而未來可能會使用的值。

3.1 使用方法

只需要實現(xiàn) New 函數(shù)即可。對象池中沒有對象時,將會調(diào)用 New 函數(shù)創(chuàng)建。

初始化 :

var studentPool = sync.Pool{
    New: func() interface{} { 
        return new(Student) 
    },
}

關(guān)鍵操作 :

// Put adds x to the pool.
func (p *Pool) Put(x any);

// Get selects an arbitrary item from the [Pool], removes it from the
// Pool, and returns it to the caller.
// Get may choose to ignore the pool and treat it as empty.
// Callers should not assume any relation between values passed to [Pool.Put] and
// the values returned by Get.
//
// If Get would otherwise return nil and p.New is non-nil, Get returns
// the result of calling p.New.
func (p *Pool) Get() any;

舉例 :

stu := studentPool.Get().(*Student)
json.Unmarshal(buf, stu)
studentPool.Put(stu)
  • Get() 用于從對象池中獲取對象,因為返回值是 interface{},因此需要類型轉(zhuǎn)換。
  • Put() 則是在對象使用完畢后,返回對象池。

3.2 底層解析

3.2.1 sync.Pool 數(shù)據(jù)結(jié)構(gòu)

type Pool struct {
    noCopy noCopy

    local     unsafe.Pointer // local fixed-size per-P pool, actual type is [P]poolLocal
    localSize uintptr        // size of the local array

    victim     unsafe.Pointer // local from previous cycle
    victimSize uintptr        // size of victims array

    // New optionally specifies a function to generate
    // a value when Get would otherwise return nil.
    // It may not be changed concurrently with calls to Get.
    New func() 
    }

• noCopy 防拷貝標志;

• local 類型為 [P]poolLocal 的數(shù)組,數(shù)組容量 P 為 goroutine 處理器 P 的個數(shù);

• victim 為經(jīng)過一輪 GC 回收,暫存的上一輪 local;類型于二級緩存 , 隨時可能被GC 回收

• New 為用戶指定的工廠函數(shù),當(dāng) Pool 內(nèi)存量元素不足時,會調(diào)用該函數(shù)構(gòu)造新的元素.

[P]poolLocal 數(shù)組
暫時存儲對象的對象池 , 每個 poolLocal 邏輯處理器分為 privatesharedList 兩部分緩存數(shù)據(jù)

type poolLocal struct {
    poolLocalInternal
}

// Local per-P Pool appendix.
type poolLocalInternal struct {
    private any       // Can be used only by the respective P.
    shared  poolChain // Local P can pushHead/popHead; any P can popTail.
}
  • poolLocal 為 Pool 中對應(yīng)于某個 P 的緩存數(shù)據(jù);
  • poolLocalInternal.private:對應(yīng)于某個 P 的私有元素,操作時無需加鎖;
  • poolLocalInternal.shared: 某個 P 下的共享元素鏈表,由于各 P 都有可能訪問,因此需要加鎖.

3.2.2 sync.Pool 的核心方法

3.2.2.1 Pool.Get

Get流程

func (p *Pool) Get() any {
    l, pid := p.pin()
    x := l.private
    l.private = nil
    if x == nil {
        x, _ = l.shared.popHead()
        if x == nil {
            x = p.getSlow(pid)
        }
    }
    runtime_procUnpin()
    if x == nil && p.New != nil {
        x = p.New()
    }
    return x
}
  • 調(diào)用 Pool.pin 方法,綁定當(dāng)前 goroutine 與 P,并且取得該 P 對應(yīng)的緩存數(shù)據(jù);
  • 嘗試獲取 P 緩存數(shù)據(jù)的私有元素 private;
  • 倘若前一步失敗,則嘗試取 P 緩存數(shù)據(jù)中共享元素鏈表的頭元素;
  • 倘若前一步失敗,則走入 Pool.getSlow 方法,嘗試取其他 P 緩存數(shù)據(jù)中共享元素鏈表的尾元素;
  • 同樣在 Pool.getSlow 方法中,倘若前一步失敗,則嘗試從上輪 gc 前緩存中取元素(victim);
  • 調(diào)用 native 方法解綁 當(dāng)前 goroutine 與 P
  • 倘若(2)-(5)步均取值失敗,調(diào)用用戶的工廠方法,進行元素構(gòu)造并返回.

3.2.2.1 Pool.Put

Put流程

/ Put adds x to the pool.
func (p *Pool) Put(x any) {
    if x == nil {
        return
    }
    l, _ := p.pin()
    if l.private == nil {
        l.private = x
    } else {
        l.shared.pushHead(x)
    }
    runtime_procUnpin()
}
  • 判斷存入元素 x 非空;
  • 調(diào)用 Pool.pin 綁定當(dāng)前 goroutine 與 P,并獲取 P 的緩存數(shù)據(jù);
  • 倘若 P 緩存數(shù)據(jù)中的私有元素為空,則將 x 置為其私有元素;
  • 倘若未走入(3)分支,則將 x 添加到 P 緩存數(shù)據(jù)共享鏈表的末尾;
  • 解綁當(dāng)前 goroutine 與 P.

3.2.2 對象池的回收

存入 pool 的對象會不定期被 go 運行時回收,因此 pool 沒有容量概念,即便大量存入元素,也不會發(fā)生內(nèi)存泄露.

具體回收時機是在 gc 時執(zhí)行的:

  • 每個 Pool 首次執(zhí)行 Get 方法時,會在內(nèi)部首次調(diào)用 pinSlow 方法內(nèi)將該 pool 添加到遷居的 allPools 數(shù)組中;
  • 每次 gc 時,會將上一輪的 oldPools 清空,并將本輪 allPools 的元素賦給 oldPools,allPools 置空;
  • 新置入 oldPools 的元素統(tǒng)一將 local 轉(zhuǎn)移到 victim,并且將 local 置為空.

綜上可以得見,最多兩輪 gc,pool 內(nèi)的對象資源將會全被回收.

到此這篇關(guān)于Go語言sync鎖與對象池的實現(xiàn)的文章就介紹到這了,更多相關(guān)Go語言sync鎖與對象池內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 淺談Golang的方法傳遞值應(yīng)該注意的地方

    淺談Golang的方法傳遞值應(yīng)該注意的地方

    這篇文章主要介紹了淺談Golang的方法傳遞值應(yīng)該注意的地方,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • golang強制類型轉(zhuǎn)換和類型斷言

    golang強制類型轉(zhuǎn)換和類型斷言

    這篇文章主要介紹了詳情介紹golang類型轉(zhuǎn)換問題,分別由介紹類型斷言和類型轉(zhuǎn)換,這兩者都是不同的概念,下面文章圍繞類型斷言和類型轉(zhuǎn)換的相關(guān)資料展開文章的詳細內(nèi)容,需要的朋友可以參考以下
    2021-12-12
  • Golang使用panic控制程序錯誤流程

    Golang使用panic控制程序錯誤流程

    這篇文章主要介紹了Golang使用panic控制程序錯誤流程,Golang panic異常處理機制中的一種流程控制方式,用于中斷程序流程并觸發(fā)異常處理
    2023-04-04
  • Go Map并發(fā)沖突預(yù)防與解決

    Go Map并發(fā)沖突預(yù)防與解決

    這篇文章主要為大家介紹了Go Map并發(fā)沖突預(yù)防與解決,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-12-12
  • 解決Goland 同一個package中函數(shù)互相調(diào)用的問題

    解決Goland 同一個package中函數(shù)互相調(diào)用的問題

    這篇文章主要介紹了解決Goland 同一個package中函數(shù)互相調(diào)用的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-05-05
  • 用golang如何替換某個文件中的字符串

    用golang如何替換某個文件中的字符串

    這篇文章主要介紹了用golang實現(xiàn)替換某個文件中的字符串操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-04-04
  • 解決Go語言time包數(shù)字與時間相乘的問題

    解決Go語言time包數(shù)字與時間相乘的問題

    這篇文章主要介紹了Go語言time包數(shù)字與時間相乘的問題,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-04-04
  • Golang 中 omitempty的作用

    Golang 中 omitempty的作用

    這篇文章主要介紹了Golang 中 omitempty的作用,文章圍繞主題展開詳細的內(nèi)容介紹,具有一定的參考一下,需要的小伙伴可以參考一下
    2022-07-07
  • 一文理解Goland協(xié)程調(diào)度器scheduler的實現(xiàn)

    一文理解Goland協(xié)程調(diào)度器scheduler的實現(xiàn)

    本文主要介紹了Goland協(xié)程調(diào)度器scheduler的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-06-06
  • Go語言并發(fā)爬蟲的具體實現(xiàn)

    Go語言并發(fā)爬蟲的具體實現(xiàn)

    本文主要介紹了Go語言并發(fā)爬蟲的具體實現(xiàn),文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-12-12

最新評論