Go sync.Pool 淺析

hi, 你們好,我是 haohongfan。html

sync.Pool 應該是 Go 裏面明星級別的數據結構,有不少優秀的文章都在介紹這個結構,本篇文章簡單剖析下 sync.Pool。不過說實話 sync.Pool 並非咱們平常開發中使用頻率很高的的併發原語。微信

儘管用的頻率很低,可是不能否認的是 sync.Pool 確實是 Go 的殺手鐗,合理使用 sync.Pool 會讓咱們的程序性能飆升。本篇文章會從使用方式,源碼剖析,運用場景等方面,讓你對 sync.Pool 有一個清晰的認知。數據結構

使用方式

sync.Pool 使用很簡單,可是想用對卻很麻煩,由於你有可能看到網上一堆錯誤的示例,各位同窗在搜索 sync.Pool 的使用例子時,要特別注意。併發

sync.Pool 是一個內存池。一般內存池是用來防止內存泄露的(例如C/C++)。sync.Pool 這個內存池卻不是幹這個的,帶 GC 功能的語言都存在垃圾回收 STW 問題,須要回收的內存塊越多,STW 持續時間就越長。若是能讓 new 出來的變量,一直不被回收,獲得重複利用,是否是就減輕了 GC 的壓力。app

正確的使用示例(下面的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 獲取內存空間,基於這個內存作相關的處理,而後再將這個內存還回(Put)到 sync.Pool。高併發

Pool 結構

sync.Pool 全景圖性能

源碼圖解

Pool.Get學習

Pool.Put優化

簡單點能夠總結成下面的流程:

Pool.Get 流程

Pool.Put流程

Pool GC 流程

Sync.Pool 梳理

Pool 的內容會清理?清理會形成數據丟失嗎?

Go 會在每一個 GC 週期內按期清理 sync.Pool 內的數據。

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

  1. 已經從 sync.Pool Get 的值,在 poolClean 時雖然說將 pool.local 置成了nil,Get 到的值依然是有效的,是被 GC 標記爲黑色的,不會被 GC回收,當 Put 後又從新加入到 sync.Pool 中

  2. 在第一個 GC 週期內 Put 到 sync.Pool 的數值,在第二個 GC 週期沒有被 Get 使用,就會被放在 local.victim 中。若是在 第三個 GC 週期仍然沒有被使用就會被 GC 回收。

runtime.GOMAXPROCS 與 pool 之間的關係?

s := p.localSize l := p.local if 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-release runtime_StoreReluintptr(&p.localSize, uintptr(size))     // store-release

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

爲何要開闢 runtime.GOMAXPROCS 個 local?

pool.local 是個 poolLocal 結構,這個結構體是 private + shared鏈表組成,在多 goroutine 的 Get/Put 下是有數據競爭的,若是隻有一個 local 就須要加鎖來操做。每一個 p 的 local 就能減小加鎖形成的數據競爭問題。

New() 的做用?假如沒有 New 會出現什麼狀況?

從上面的 pool.Get 流程圖能夠看出來,從 sync.Pool 獲取一個內存會嘗試從當前 private,shared,其餘的 p 的 shared 獲取或者 victim 獲取,若是實在獲取不到時,纔會調用 New 函數來獲取。也就是 New() 函數纔是真正開闢內存空間的。New() 開闢出來的的內存空間使用完畢後,調用 pool.Put 函數放入到 sync.Pool 中被重複利用。

若是 New 函數沒有被初始化會怎樣呢?很明顯,sync.Pool 就廢掉了,由於沒有了初始化內存的地方了。

先 Put,再 Get 會出現什麼狀況?

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

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

若是你直接跑這個例子,能獲得你想像的結果,可是在某些狀況下就不是這個結果了。

在 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 的數據結構就不支持作這個事情。

前面說使用 sync.Pool 容易被錯誤示例誤導,就是上面這個寫法。爲何 Put 的值 再 Get 會出現問題?

  • 狀況1:sync.Pool 的 poolCleanup 函數在系統 GC 時會被調用,Put 到 sync.Pool 的值,因爲有可能一直得不到利用,被在某個 GC 週期內就有可能被釋放掉了。

  • 狀況2:不一樣的 goroutine 綁定的 p 有多是不同的,當前 p 對應的 goroutine 放入到 sync.Pool 的值有可能被其餘的 p 對應的 goroutine 取到,致使當前 goroutine 再也取不到這個值。

  • 狀況3:使用 runtime.GOMAXPROCS(N) 來改變 p 的數量,會使 sync.Pool 的 pool.poolLocal 釋放從新開闢新的空間,致使 sync.Pool 被釋放掉。

  • 狀況4:還有不少狀況

只 Get 不 Put 會內存泄露嗎?

使用其餘的池,如鏈接池,若是取鏈接使用後不放回鏈接池,就會出現鏈接池泄露,「是否是 sync.Pool 也有這個問題呢?」

經過上面的流程圖,能夠看出來 Pool.Get 的時候會嘗試從當前 private,shared,其餘的 p 的 shared 獲取或者 victim 獲取,若是實在獲取不到時,纔會調用 New 函數來獲取,New 出來的內容自己仍是受系統 GC 來控制的。因此若是咱們提供的 New 實現不存在內存泄露的話,那麼 sync.Pool 是不會內存泄露的。當 New 出來的變量若是再也不被使用,就會被系統 GC 給回收掉。

若是不 Put 回 sync.Pool,會形成 Get 的時候每次都調用的 New 來從堆棧申請空間,達不到減輕 GC 壓力。

使用場景

上面說到 sync.Pool 業務開發中不是一個經常使用結構,咱們業務開發中不必假想某塊代碼會有強烈的性能問題,一上來就用 sync.Pool 硬懟。sync.Pool 主要是爲了解決 Go GC 壓力過大問題的,因此通常狀況下,當線上高併發業務出現 GC 問題須要被優化時,才須要用 sync.Pool 出場。

使用注意點

  1. sync.Pool 一樣不能被複制。

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

本文基於的 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 是如何優化的? https://colobu.com/2019/10/08/how-is-sync-Pool-improved-in-Go-1-13/

sync.Pool 的剖析到這裏基本就寫完了,想跟我交流的能夠在評論區留言。


sync.Pool 完整流程圖獲取連接:連接: https://pan.baidu.com/s/1T5e8qCzp8JcTgARZFjGQoQ  密碼: ngea 其餘模塊流程圖,請關注公衆號回覆1獲取。學習資料分享,關注公衆號回覆指令:

  • 回覆 0,獲取 《Go 面經》

  • 回覆 1,獲取 《Go 源碼流程圖》

     

本文分享自微信公衆號 - HHFCodeRv(hhfcodearts)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索