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

深入淺出Golang中的sync.Pool

 更新時(shí)間:2023年03月13日 14:03:11   作者:社恐的小馬同學(xué)  
sync.Pool是可伸縮的,也是并發(fā)安全的,其大小僅受限于內(nèi)存大小。本文主要為大家介紹一下Golang中sync.Pool的原理與使用,感興趣的小伙伴可以了解一下

學(xué)習(xí)到的內(nèi)容:

1.一個(gè)64位的int類型值,充分利用高32位和低32位,進(jìn)行相關(guān)加減以及從一個(gè)64位中拆出高32位和低32位.

擴(kuò)展:如何自己實(shí)現(xiàn)一個(gè)無(wú)鎖隊(duì)列.

  • 如何判斷隊(duì)列是否滿.
  • 如何實(shí)現(xiàn)無(wú)鎖化.
  • 優(yōu)化方面需要思考的東西.

2.內(nèi)存相關(guān)操作以及優(yōu)化

  • 內(nèi)存對(duì)齊
  • CPU Cache Line
  • 直接操作內(nèi)存.

一、原理分析

1.1 結(jié)構(gòu)依賴關(guān)系圖

下面是相關(guān)源代碼,不過(guò)是已經(jīng)刪減了對(duì)本次分析沒有用的代碼.

type Pool struct {
    // GMP中,每一個(gè)P(協(xié)程調(diào)度器)會(huì)有一個(gè)數(shù)組,數(shù)組大小位localSize. 
 local     unsafe.Pointer 
 // p 數(shù)組大小.
 localSize uintptr
 New func() any
}

// poolLocal 每個(gè)P(協(xié)程調(diào)度器)的本地pool.
type poolLocal struct {
 poolLocalInternal
    // 保證一個(gè)poolLocal占用一個(gè)緩存行
 pad [128 - unsafe.Sizeof(poolLocalInternal{})%128]byte
}

type poolLocalInternal struct {
 private any       // Can be used only by the respective P. 16
 shared  poolChain // Local P can pushHead/popHead; any P can popTail. 8
}

type poolChain struct {
 head *poolChainElt
 tail *poolChainElt
}

type poolChainElt struct {
 poolDequeue
 next, prev *poolChainElt
}

type poolDequeue struct {
 // head 高32位,tail低32位.
 headTail uint64
 vals []eface
}

// 存儲(chǔ)具體的value. 
type eface struct {
 typ, val unsafe.Pointer
}

1.2 用圖讓代碼說(shuō)話

1.3 Put過(guò)程分析

Put 過(guò)程分析比較重要,因?yàn)檫@里會(huì)包含pool所有依賴相關(guān)分析.

總的分析學(xué)習(xí)過(guò)程可以分為下面幾個(gè)步驟:

1.獲取P對(duì)應(yīng)的poolLocal

2.val如何進(jìn)入poolLocal下面的poolDequeue隊(duì)列中的.

3.如果當(dāng)前協(xié)程獲取到當(dāng)前P對(duì)應(yīng)的poolLocal之后進(jìn)行put前,協(xié)程讓出CPU使用權(quán),再次調(diào)度過(guò)來(lái)之后,會(huì)發(fā)生什么?

4.讀寫內(nèi)存優(yōu)化.

數(shù)組直接操作內(nèi)存,而不經(jīng)過(guò)Golang

充分利用uint64值的特性,將headtail用一個(gè)值來(lái)進(jìn)行表示,減少CPU訪問(wèn)內(nèi)存次數(shù).

獲取P對(duì)應(yīng)的poolLocal

sync.Pool.local其實(shí)是一個(gè)指針,并且通過(guò)變量+結(jié)構(gòu)體大小來(lái)劃分內(nèi)存空間,從而將這片內(nèi)存直接劃分為數(shù)組. Go 在Put之前會(huì)先對(duì)當(dāng)前Goroutine綁定到當(dāng)前P中,然后通過(guò)pid獲取其在local內(nèi)存地址中的歧視指針,在獲取時(shí)是會(huì)進(jìn)行內(nèi)存分配的. 具體如下:

func (p *Pool) pin() (*poolLocal, int) {
 // 返回運(yùn)行當(dāng)前協(xié)程的P(協(xié)程調(diào)度器),并且設(shè)置禁止搶占.
 pid := runtime_procPin()
 s := runtime_LoadAcquintptr(&p.localSize) // load-acquire
 l := p.local                              // load-consume
 // pid < 核心數(shù). 默認(rèn)走該邏輯.
 if uintptr(pid) < s {
  return indexLocal(l, pid), pid
 }
 // 設(shè)置的P大于本機(jī)CPU核心數(shù).
 return p.pinSlow()
}

// indexLocal 獲取當(dāng)前P的poolLocal指針. 
func indexLocal(l unsafe.Pointer, i int) *poolLocal {
 // l p.local指針開始位置.
 // 我猜測(cè)這里如果l為空,編譯階段會(huì)進(jìn)行優(yōu)化. 
 lp := unsafe.Pointer(uintptr(l) + uintptr(i)*unsafe.Sizeof(poolLocal{}))
 // uintptr真實(shí)的指針.
 // unsafe.Pointer Go對(duì)指針的封裝: 用于指針和結(jié)構(gòu)體互相轉(zhuǎn)化.
 return (*poolLocal)(lp)
}

從上面代碼我們可以看到,Go通過(guò)runtime_procPin來(lái)設(shè)置當(dāng)前Goroutine獨(dú)占P,并且直接通過(guò)頭指針+偏移量(數(shù)組結(jié)構(gòu)體大小)來(lái)進(jìn)行對(duì)內(nèi)存劃分為數(shù)組.

Put 進(jìn)入poolDequeue隊(duì)列:

Go在Push時(shí),會(huì)通過(guò)headtail來(lái)獲取當(dāng)前隊(duì)列內(nèi)元素個(gè)數(shù),如果滿了,則會(huì)重新構(gòu)建一個(gè)環(huán)型隊(duì)列poolChainElt,并且設(shè)置為poolChain.head,并且賦值next以及prev.

通過(guò)下面代碼,我們可以看到,Go通過(guò)邏輯運(yùn)算判斷隊(duì)列是否滿的設(shè)計(jì)時(shí)非常巧妙的,如果后續(xù)我們?nèi)ラ_發(fā)組件,也是可以這么進(jìn)行設(shè)計(jì)的。

func (c *poolChain) pushHead(val any) {
 d := c.head
    // 初始化. 
 if d == nil {
  // Initialize the chain.
  const initSize = 8 // Must be a power of 2
  d = new(poolChainElt)
  d.vals = make([]eface, initSize)
  c.head = d
  // 將新構(gòu)建的d賦值給tail.
  storePoolChainElt(&c.tail, d)
 }
 // 入隊(duì).
 if d.pushHead(val) {
  return
 }
 // 隊(duì)列滿了.
 newSize := len(d.vals) * 2
 if newSize >= dequeueLimit {
        // 隊(duì)列大小默認(rèn)為2的30次方. 
  newSize = dequeueLimit
 }

    // 賦值鏈表前后節(jié)點(diǎn)關(guān)系. 
 // prev.
 // d2.prev=d1.
 // d1.next=d2.
 d2 := &poolChainElt{prev: d}
 d2.vals = make([]eface, newSize)
 c.head = d2
 // next .
 storePoolChainElt(&d.next, d2)
 d2.pushHead(val)
}

// 入隊(duì)poolDequeue
func (d *poolDequeue) pushHead(val any) bool {
 ptrs := atomic.LoadUint64(&d.headTail)
 head, tail := d.unpack(ptrs)
 // head 表示當(dāng)前有多少元素.
 if (tail+uint32(len(d.vals)))&(1<<dequeueBits-1) == head {
  return false
 }
 // 環(huán)型隊(duì)列. head&uint32(len(d.vals)-1) 表示當(dāng)前元素落的位置一定在隊(duì)列上.
 slot := &d.vals[head&uint32(len(d.vals)-1)]

 typ := atomic.LoadPointer(&slot.typ)
 if typ != nil {
  return false
 }

 // The head slot is free, so we own it.
 if val == nil {
  val = dequeueNil(nil)
 }
    // 向slot寫入指針類型為*any,并且值為val.
 *(*any)(unsafe.Pointer(slot)) = val
    // headTail高32位++
 atomic.AddUint64(&d.headTail, 1<<dequeueBits)
 return true
}

Get實(shí)現(xiàn)邏輯:

其實(shí)我們看了Put相關(guān)邏輯之后,我們可能很自然的就想到了Get的邏輯,無(wú)非就是遍歷鏈表,并且如果隊(duì)列中最后一個(gè)元素不為空,則會(huì)將該元素返回,并且將該插槽賦值為空值.

二、學(xué)習(xí)收獲

如何自己實(shí)現(xiàn)一個(gè)無(wú)鎖隊(duì)列. 本文未實(shí)現(xiàn),后續(xù)文章會(huì)進(jìn)行實(shí)現(xiàn).

2.1 如何自己實(shí)現(xiàn)一個(gè)無(wú)鎖隊(duì)列

橫向思考,并未進(jìn)行實(shí)現(xiàn),后續(xù)會(huì)進(jìn)行實(shí)現(xiàn)“

  • 存儲(chǔ)直接使用指針來(lái)進(jìn)行存儲(chǔ),充分利用uintptrunsafe.Pointer和結(jié)構(gòu)體指針之間的依賴關(guān)系來(lái)提升性能.
  • 狀態(tài)存儲(chǔ)要考慮CPU Cache Line、內(nèi)存對(duì)齊以及減少訪問(wèn)內(nèi)存次數(shù)等相關(guān)問(wèn)題.
  • 充分利用Go中的原子操作包來(lái)進(jìn)行實(shí)現(xiàn),通過(guò)atomic.CompareAndSwapPointer來(lái)設(shè)計(jì)自旋來(lái)達(dá)到無(wú)鎖化.

到此這篇關(guān)于深入淺出Golang中的sync.Pool的文章就介紹到這了,更多相關(guān)Golang sync.Pool內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • golang中defer執(zhí)行時(shí)機(jī)的案例分析

    golang中defer執(zhí)行時(shí)機(jī)的案例分析

    這篇文章主要來(lái)通過(guò)一些案例和大家一起探討一下golang中defer的執(zhí)行時(shí)機(jī),文中的示例代碼講解詳細(xì),對(duì)我們深入了解golang有一定的幫助,感興趣的可以跟隨小編一起學(xué)習(xí)一下
    2023-11-11
  • Golang中interface{}轉(zhuǎn)為數(shù)組的操作

    Golang中interface{}轉(zhuǎn)為數(shù)組的操作

    這篇文章主要介紹了Golang中interface{}轉(zhuǎn)為數(shù)組的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2021-04-04
  • Go項(xiàng)目與Docker結(jié)合實(shí)現(xiàn)高效部署深入探究

    Go項(xiàng)目與Docker結(jié)合實(shí)現(xiàn)高效部署深入探究

    在現(xiàn)代軟件開發(fā)中,使用Docker部署應(yīng)用程序已經(jīng)成為一種標(biāo)準(zhǔn)實(shí)踐,本文將深入探討如何將Go項(xiàng)目與Docker結(jié)合,實(shí)現(xiàn)高效、可靠的部署過(guò)程,通過(guò)詳細(xì)的步驟和豐富的示例,你將能夠迅速掌握這一流程
    2023-12-12
  • 解決Golang并發(fā)工具Singleflight的問(wèn)題

    解決Golang并發(fā)工具Singleflight的問(wèn)題

    前段時(shí)間在一個(gè)項(xiàng)目里使用到了分布式鎖進(jìn)行共享資源的訪問(wèn)限制,后來(lái)了解到Golang里還能夠使用singleflight對(duì)共享資源的訪問(wèn)做限制,于是利用空余時(shí)間了解,將知識(shí)沉淀下來(lái),并做分享
    2022-05-05
  • golang守護(hù)進(jìn)程用法示例

    golang守護(hù)進(jìn)程用法示例

    這篇文章主要介紹了golang守護(hù)進(jìn)程用法,結(jié)合實(shí)例形式分析了Go語(yǔ)言守護(hù)進(jìn)程的具體實(shí)現(xiàn)與使用技巧,需要的朋友可以參考下
    2016-07-07
  • Go語(yǔ)言生成素?cái)?shù)的方法

    Go語(yǔ)言生成素?cái)?shù)的方法

    這篇文章主要介紹了Go語(yǔ)言生成素?cái)?shù)的方法,實(shí)例分析了Go語(yǔ)言生成素?cái)?shù)的技巧,需要的朋友可以參考下
    2015-03-03
  • go語(yǔ)言實(shí)現(xiàn)通過(guò)FTP庫(kù)自動(dòng)上傳web日志

    go語(yǔ)言實(shí)現(xiàn)通過(guò)FTP庫(kù)自動(dòng)上傳web日志

    這篇文章主要介紹了go語(yǔ)言實(shí)現(xiàn)通過(guò)FTP庫(kù)自動(dòng)上傳web日志,非常簡(jiǎn)單實(shí)用,需要的小伙伴快來(lái)參考下吧。
    2015-03-03
  • golang實(shí)現(xiàn)對(duì)JavaScript代碼混淆

    golang實(shí)現(xiàn)對(duì)JavaScript代碼混淆

    在Go語(yǔ)言中,你可以使用一些工具來(lái)混淆JavaScript代碼,一個(gè)常用的工具是Terser,它可以用于壓縮和混淆JavaScript代碼,你可以通過(guò)Go語(yǔ)言的`os/exec`包來(lái)調(diào)用Terser工具,本文給通過(guò)一個(gè)簡(jiǎn)單的示例給大家介紹一下,感興趣的朋友可以參考下
    2024-01-01
  • Go??iota?常量基本語(yǔ)法介紹

    Go??iota?常量基本語(yǔ)法介紹

    這篇文章主要介紹了Go?為什么要設(shè)計(jì)?iota?常量,我們介紹了 Go 中 iota 的基本語(yǔ)法。同時(shí)基于歷史資料針對(duì) iota 到底是什么,為什么要這么叫,又有什么用進(jìn)行了一番研究,需要的朋友可以參考下
    2022-06-06
  • Go語(yǔ)言Gin框架獲取請(qǐng)求參數(shù)的兩種方式

    Go語(yǔ)言Gin框架獲取請(qǐng)求參數(shù)的兩種方式

    在添加路由處理函數(shù)之后,就可以在路由處理函數(shù)中編寫業(yè)務(wù)處理代碼了,而編寫業(yè)務(wù)代碼第一件事一般就是獲取HTTP請(qǐng)求的參數(shù)吧,Gin框架在net/http包的基礎(chǔ)上封裝了獲取參數(shù)的方式,本文小編給大家介紹了獲取參數(shù)的兩種方式,需要的朋友可以參考下
    2024-01-01

最新評(píng)論

多伦县| 嘉兴市| 繁峙县| 兴义市| 丹棱县| 隆林| 温州市| 白玉县| 滦南县| 上饶市| 华容县| 益阳市| 临朐县| 分宜县| 威远县| 绥阳县| 易门县| 哈密市| 嘉义市| 丰顺县| 泗水县| 怀宁县| 子长县| 达州市| 耒阳市| 颍上县| 腾冲县| 鸡泽县| 福建省| 灌阳县| 亳州市| 临高县| 江油市| 靖安县| 门头沟区| 阿瓦提县| 桐梓县| 泾源县| 青州市| 锦屏县| 兰溪市|