您好,登錄后才能下訂單哦!
本篇內容主要講解“Go sync.Pool的原理及作用是什么”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“Go sync.Pool的原理及作用是什么”吧!
sync.Pool 使用很簡單,但是想用對卻很麻煩,因為你有可能看到網上一堆錯誤的示例,各位同學在搜索 sync.Pool 的使用例子時,要特別注意。
sync.Pool 是一個內存池。通常內存池是用來防止內存泄露的(例如C/C++)。sync.Pool 這個內存池卻不是干這個的,帶 GC 功能的語言都存在垃圾回收 STW 問題,需要回收的內存塊越多,STW 持續時間就越長。如果能讓 new 出來的變量,一直不被回收,得到重復利用,是不是就減輕了 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 獲取內存空間,基于這個內存做相關的處理,然后再將這個內存還回(Put)到 sync.Pool。
sync.Pool 全景圖
Pool.Get
Pool.Put
簡單點可以總結成下面的流程:
Pool.Get 流程
Pool.Put流程
Pool GC 流程
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 回收。
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) 是獲取當前最大的 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 就能減少加鎖造成的數據競爭問題。
從上面的 pool.Get 流程圖可以看出來,從 sync.Pool 獲取一個內存會嘗試從當前 private,shared,其他的 p 的 shared 獲取或者 victim 獲取,如果實在獲取不到時,才會調用 New 函數來獲取。也就是 New() 函數才是真正開辟內存空間的。New() 開辟出來的的內存空間使用完畢后,調用 pool.Put 函數放入到 sync.Pool 中被重復利用。
如果 New 函數沒有被初始化會怎樣呢?很明顯,sync.Pool 就廢掉了,因為沒有了初始化內存的地方了。
「一定要注意,下面這個例子的用法是錯誤的」
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:還有很多情況
使用其他的池,如連接池,如果取連接使用后不放回連接池,就會出現連接池泄露,「是不是 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/
到此,相信大家對“Go sync.Pool的原理及作用是什么”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。