溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Go sync.Pool的原理及作用是什么

發(fā)布時間:2021-06-22 17:34:08 來源:億速云 閱讀:348 作者:chen 欄目:編程語言

本篇內(nèi)容主要講解“Go sync.Pool的原理及作用是什么”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學(xué)習(xí)“Go sync.Pool的原理及作用是什么”吧!

使用方式

sync.Pool 使用很簡單,但是想用對卻很麻煩,因為你有可能看到網(wǎng)上一堆錯誤的示例,各位同學(xué)在搜索 sync.Pool 的使用例子時,要特別注意。

sync.Pool 是一個內(nèi)存池。通常內(nèi)存池是用來防止內(nèi)存泄露的(例如C/C++)。sync.Pool 這個內(nèi)存池卻不是干這個的,帶 GC 功能的語言都存在垃圾回收 STW 問題,需要回收的內(nèi)存塊越多,STW 持續(xù)時間就越長。如果能讓 new 出來的變量,一直不被回收,得到重復(fù)利用,是不是就減輕了 GC 的壓力。

正確的使用示例(下面的demo選自gin)

func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {    c := engine.pool.Get().(*Context)
    c.writermem.reset(w)
    c.Request = req
    c.reset()

    engine.handleHTTPRequest(c)

    engine.pool.Put(c)}

一定要注意的是:是先 Get 獲取內(nèi)存空間,基于這個內(nèi)存做相關(guān)的處理,然后再將這個內(nèi)存還回(Put)到 sync.Pool。

Pool 結(jié)構(gòu)

Go sync.Pool的原理及作用是什么

sync.Pool 全景圖

源碼圖解

Go sync.Pool的原理及作用是什么

Pool.Get

Go sync.Pool的原理及作用是什么

Pool.Put

簡單點可以總結(jié)成下面的流程:

Go sync.Pool的原理及作用是什么

Pool.Get 流程

Go sync.Pool的原理及作用是什么

Pool.Put流程

Go sync.Pool的原理及作用是什么

Pool GC 流程

Sync.Pool 梳理

Pool 的內(nèi)容會清理?清理會造成數(shù)據(jù)丟失嗎?

Go 會在每個 GC 周期內(nèi)定期清理 sync.Pool 內(nèi)的數(shù)據(jù)。

要分幾個方面來說這個問題。

  1. 已經(jīng)從 sync.Pool Get 的值,在 poolClean 時雖說將 pool.local 置成了nil,Get 到的值依然是有效的,是被 GC 標(biāo)記為黑色的,不會被 GC回收,當(dāng) Put 后又重新加入到 sync.Pool 中

  2. 在第一個 GC 周期內(nèi) Put 到 sync.Pool 的數(shù)值,在第二個 GC 周期沒有被 Get 使用,就會被放在 local.victim 中。如果在 第三個 GC 周期仍然沒有被使用就會被 GC 回收。

runtime.GOMAXPROCS 與 pool 之間的關(guān)系?

s := p.localSize
l := p.localif uintptr(pid) < s {
    return indexLocal(l, pid), pid
}if p.local == nil {
    allPools = append(allPools, p)
}// If GOMAXPROCS changes between GCs, we re-allocate the array and lose the old one.size := runtime.GOMAXPROCS(0)
local := make([]poolLocal, size)
atomic.StorePointer(&p.local, unsafe.Pointer(&local[0])) // store-releaseruntime_StoreReluintptr(&p.localSize, uintptr(size))     // store-release

runtime.GOMAXPROCS(0) 是獲取當(dāng)前最大的 p 的數(shù)量。sync.Pool 的 poolLocal 數(shù)量受 p 的數(shù)量影響,會開辟 runtime.GOMAXPROCS(0) 個 poolLocal。某些場景下我們會使用 runtime.GOMAXPROCS(N) 來改變 p 的數(shù)量,會使 sync.Pool 的 pool.poolLocal 釋放重新開辟新的空間。

為什么要開辟 runtime.GOMAXPROCS 個 local?

pool.local 是個 poolLocal 結(jié)構(gòu),這個結(jié)構(gòu)體是 private + shared鏈表組成,在多 goroutine 的 Get/Put 下是有數(shù)據(jù)競爭的,如果只有一個 local 就需要加鎖來操作。每個 p 的 local 就能減少加鎖造成的數(shù)據(jù)競爭問題。

New() 的作用?假如沒有 New 會出現(xiàn)什么情況?

從上面的 pool.Get 流程圖可以看出來,從 sync.Pool 獲取一個內(nèi)存會嘗試從當(dāng)前 private,shared,其他的 p 的 shared 獲取或者 victim 獲取,如果實在獲取不到時,才會調(diào)用 New 函數(shù)來獲取。也就是 New() 函數(shù)才是真正開辟內(nèi)存空間的。New() 開辟出來的的內(nèi)存空間使用完畢后,調(diào)用 pool.Put 函數(shù)放入到 sync.Pool 中被重復(fù)利用。

如果 New 函數(shù)沒有被初始化會怎樣呢?很明顯,sync.Pool 就廢掉了,因為沒有了初始化內(nèi)存的地方了。

先 Put,再 Get 會出現(xiàn)什么情況?

「一定要注意,下面這個例子的用法是錯誤的」

func main(){
    pool:= sync.Pool{
        New: func() interface{} {
            return item{}
        },
    }
    pool.Put(item{value:1})
    data := pool.Get()
    fmt.Println(data)
}

如果你直接跑這個例子,能得到你想像的結(jié)果,但是在某些情況下就不是這個結(jié)果了。

在 Pool.Get 注釋里面有這么一句話:“Callers should not assume any relation between values passed to Put and the values returned by Get.”,告訴我們不能把值 Pool.Put 到 sync.Pool 中,再使用 Pool.Get 取出來,因為 sync.Pool 不是 map 或者 slice,放入的值是有可能拿不到的,sync.Pool 的數(shù)據(jù)結(jié)構(gòu)就不支持做這個事情。

前面說使用 sync.Pool 容易被錯誤示例誤導(dǎo),就是上面這個寫法。為什么 Put 的值 再 Get 會出現(xiàn)問題?

  • 情況1:sync.Pool 的 poolCleanup 函數(shù)在系統(tǒng) GC 時會被調(diào)用,Put 到 sync.Pool 的值,由于有可能一直得不到利用,被在某個 GC 周期內(nèi)就有可能被釋放掉了。

  • 情況2:不同的 goroutine 綁定的 p 有可能是不一樣的,當(dāng)前 p 對應(yīng)的 goroutine 放入到 sync.Pool 的值有可能被其他的 p 對應(yīng)的 goroutine 取到,導(dǎo)致當(dāng)前 goroutine 再也取不到這個值。

  • 情況3:使用 runtime.GOMAXPROCS(N) 來改變 p 的數(shù)量,會使 sync.Pool 的 pool.poolLocal 釋放重新開辟新的空間,導(dǎo)致 sync.Pool 被釋放掉。

  • 情況4:還有很多情況

只 Get 不 Put 會內(nèi)存泄露嗎?

使用其他的池,如連接池,如果取連接使用后不放回連接池,就會出現(xiàn)連接池泄露,「是不是 sync.Pool 也有這個問題呢?」

通過上面的流程圖,可以看出來 Pool.Get 的時候會嘗試從當(dāng)前 private,shared,其他的 p 的 shared 獲取或者 victim 獲取,如果實在獲取不到時,才會調(diào)用 New 函數(shù)來獲取,New 出來的內(nèi)容本身還是受系統(tǒng) GC 來控制的。所以如果我們提供的 New 實現(xiàn)不存在內(nèi)存泄露的話,那么 sync.Pool 是不會內(nèi)存泄露的。當(dāng) New 出來的變量如果不再被使用,就會被系統(tǒng) GC 給回收掉。

如果不 Put 回 sync.Pool,會造成 Get 的時候每次都調(diào)用的 New 來從堆棧申請空間,達(dá)不到減輕 GC 壓力。

使用場景

上面說到 sync.Pool 業(yè)務(wù)開發(fā)中不是一個常用結(jié)構(gòu),我們業(yè)務(wù)開發(fā)中沒必要假想某塊代碼會有強烈的性能問題,一上來就用 sync.Pool 硬懟。sync.Pool 主要是為了解決 Go GC 壓力過大問題的,所以一般情況下,當(dāng)線上高并發(fā)業(yè)務(wù)出現(xiàn) GC 問題需要被優(yōu)化時,才需要用 sync.Pool 出場。

使用注意點

  1. sync.Pool 同樣不能被復(fù)制。

  2. 好的使用習(xí)慣,從 pool.Get 出來的值進(jìn)行數(shù)據(jù)的清空(reset),防止垃圾數(shù)據(jù)污染。

?  

本文基于的 Go 源碼版本:1.16.2

?

參考鏈接

  1. 深度解密 Go 語言之 sync.Pool https://www.cnblogs.com/qcrao-2018/p/12736031.html

  2. 請問sync.Pool有什么缺點? https://mp.weixin.qq.com/s/2ZC1BWTylIZMmuQ3HwrnUg

  3. Go 1.13中 sync.Pool 是如何優(yōu)化的? https://colobu.com/2019/10/08/how-is-sync-Pool-improved-in-Go-1-13/

到此,相信大家對“Go sync.Pool的原理及作用是什么”有了更深的了解,不妨來實際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI