sync.Pool
給了一大段註釋來講明 pool
是啥,咱們看看這段都說了些什麼。web
臨時對象池是一些能夠分別存儲和取出的臨時對象。緩存
池中的對象會在沒有任何通知的狀況下被移出(釋放或者從新取出使用)。若是 pool
中持有某個對象的惟一引用,則該對象極可能會被回收。安全
Pool
在多 goroutine
使用環境中是安全的。數據結構
Pool
是用來緩存已經申請了的 目前未使用的 接下來可能會使用的 內存,以此緩解 GC
壓力。使用它能夠方便高效的構建線程安全的 free list
(一種用於動態內存申請的數據結構)。然而,它並不適合全部場景的 free list
。併發
在同一 package
中獨立運行的多個獨立線程之間靜默共享一組臨時元素纔是 pool
的合理使用場景。Pool
提供在多個獨立 client
之間共享臨時元素的機制。app
在 fmt
包中有一個使用 Pool
的例子,它維護了一個動態大小的輸出 buffer
。dom
另外,一些短生命週期的對象不適合使用 pool
來維護,這種狀況下使用 pool
不划算。這是應該使用它們本身的 free list
(這裏可能指的是 go 內存模型中用於緩存 <32k小對象的 free list) 更高效。函數
Pool
一旦使用,不能被複制。ui
Pool
結構體的定義爲:atom
type Pool struct { noCopy noCopy local unsafe.Pointer // 本地P緩存池指針 localSize uintptr // 本地P緩存池大小 // 當池中沒有可能對象時 // 會調用 New 函數構造構造一個對象 New func() interface{} }
Pool
中有兩個定義的公共方法,分別是 Put
- 向池中添加元素;Get
- 從池中獲取元素,若是沒有,則調用 New
生成元素,若是 New
未設置,則返回 nil
。
Pool
會爲每一個 P
維護一個本地池,P
的本地池分爲 私有池 private
和共享池 shared
。私有池中的元素只能本地 P
使用,共享池中的元素可能會被其餘 P
偷走,因此使用私有池 private
時不用加鎖,而使用共享池 shared
時需加鎖。
Get
會優先查找本地 private
,再查找本地 shared
,最後查找其餘 P
的 shared
,若是以上所有沒有可用元素,最後會調用 New
函數獲取新元素。
func (p *Pool) Get() interface{} { if race.Enabled { race.Disable() } // 獲取本地 P 的 poolLocal 對象 l := p.pin() // 先獲取 private 池中的對象(只有一個) x := l.private l.private = nil runtime_procUnpin() if x == nil { // 查找本地 shared 池, // 本地 shared 可能會被其餘 P 訪問 // 須要加鎖 l.Lock() last := len(l.shared) - 1 if last >= 0 { x = l.shared[last] l.shared = l.shared[:last] } l.Unlock() // 查找其餘 P 的 shared 池 if x == nil { x = p.getSlow() } } if race.Enabled { race.Enable() if x != nil { race.Acquire(poolRaceAddr(x)) } } // 未找到可用元素,調用 New 生成 if x == nil && p.New != nil { x = p.New() } return x }
getSlow
,從其餘 P
中的 shared
池中獲取可用元素:
func (p *Pool) getSlow() (x interface{}) { // See the comment in pin regarding ordering of the loads. size := atomic.LoadUintptr(&p.localSize) // load-acquire local := p.local // load-consume // Try to steal one element from other procs. pid := runtime_procPin() runtime_procUnpin() for i := 0; i < int(size); i++ { l := indexLocal(local, (pid+i+1)%int(size)) // 對應 pool 需加鎖 l.Lock() last := len(l.shared) - 1 if last >= 0 { x = l.shared[last] l.shared = l.shared[:last] l.Unlock() break } l.Unlock() } return x }
Put
優先把元素放在 private
池中;若是 private
不爲空,則放在 shared
池中。有趣的是,在入池以前,該元素有 1/4 可能被丟掉。
func (p *Pool) Put(x interface{}) { if x == nil { return } if race.Enabled { if fastrand()%4 == 0 { // 隨機把元素扔掉... // Randomly drop x on floor. return } race.ReleaseMerge(poolRaceAddr(x)) race.Disable() } l := p.pin() if l.private == nil { l.private = x x = nil } runtime_procUnpin() if x != nil { // 共享池訪問,須要加鎖 l.Lock() l.shared = append(l.shared, x) l.Unlock() } if race.Enabled { race.Enable() } }
當世界暫停,垃圾回收將要開始時, poolCleanup
會被調用。該函數內不能分配內存且不能調用任何運行時函數。緣由:
防止錯誤的保留整個 Pool
若是 GC
發生時,某個 goroutine
正在訪問 l.shared
,整個 Pool
將會保留,下次執行時將會有雙倍內存
func poolCleanup() { for i, p := range allPools { allPools[i] = nil for i := 0; i < int(p.localSize); i++ { l := indexLocal(p.local, i) l.private = nil for j := range l.shared { l.shared[j] = nil } l.shared = nil } p.local = nil p.localSize = 0 } allPools = []*Pool{} }
在 web
應用中,後臺在處理用戶的每條請求時都會爲當前請求建立一個上下文環境 Context
,用於存儲請求信息及相應信息等。Context
知足長生命週期的特色,且用戶請求也是屬於併發環境,因此對於線程安全的 Pool
很是適合用來維護 Context
的臨時對象池。
Gin
在結構體 Engine
中定義了一個 pool
:
type Engine struct { // ... 省略了其餘字段 pool sync.Pool }
初始化 engine
時定義了 pool
的 New
函數:
engine.pool.New = func() interface{} { return engine.allocateContext() } // allocateContext func (engine *Engine) allocateContext() *Context { // 構造新的上下文對象 return &Context{engine: engine} }
ServeHttp:
// 從 pool 中獲取,並轉化爲 *Context c := engine.pool.Get().(*Context) c.writermem.reset(w) c.Request = req c.reset() // reset engine.handleHTTPRequest(c) // 再扔回 pool 中 engine.pool.Put(c)
printer
也符合長生命週期的特色,同時也會可能會在多 goroutine
中使用,因此也適合使用 pool
來維護。
printer
與 它的臨時對象池
// pp 用來維護 printer 的狀態 // 它經過 sync.Pool 來重用,避免申請內存 type pp struct { //... 字段已省略 } var ppFree = sync.Pool{ New: func() interface{} { return new(pp) }, }
獲取與釋放:
func newPrinter() *pp { p := ppFree.Get().(*pp) p.panicking = false p.erroring = false p.fmt.init(&p.buf) return p } func (p *pp) free() { p.buf = p.buf[:0] p.arg = nil p.value = reflect.Value{} ppFree.Put(p) }