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 內的數據。
要分幾個方面來講這個問題。
-
已經從 sync.Pool Get 的值,在 poolClean 時雖然說將 pool.local 置成了nil,Get 到的值依然是有效的,是被 GC 標記爲黑色的,不會被 GC回收,當 Put 後又從新加入到 sync.Pool 中
-
在第一個 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 出場。
使用注意點
-
sync.Pool 一樣不能被複制。
-
好的使用習慣,從 pool.Get 出來的值進行數據的清空(reset),防止垃圾數據污染。
❝本文基於的 Go 源碼版本:1.16.2
❞
參考連接
-
深度解密 Go 語言之 sync.Pool https://www.cnblogs.com/qcrao-2018/p/12736031.html
-
請問sync.Pool有什麼缺點? https://mp.weixin.qq.com/s/2ZC1BWTylIZMmuQ3HwrnUg
-
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源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。