臨時對象池 pool 是啥?
sync.Pool
給了一大段註釋來說明 pool
是啥,我們看看這段都說了些什麼。
臨時對象池是一些可以分別存儲和取出的臨時對象。
池中的對象會在沒有任何通知的情況下被移出(釋放或者重新取出使用)。如果 pool
中持有某個對象的唯一引用,則該對象很可能會被回收。
Pool
在多 goroutine
使用環境中是安全的。
Pool
是用來緩存已經申請了的 目前未使用的 接下來可能會使用的 內存,以此緩解 GC
壓力。使用它可以方便高效的構建線程安全的 free list
(一種用於動態內存申請的數據結構)。然而,它並不適合所有場景的 free list
。
在同一 package
中獨立運行的多個獨立線程之間靜默共享一組臨時元素纔是 pool
的合理使用場景。Pool
提供在多個獨立 client
之間共享臨時元素的機制。
在 fmt
包中有一個使用 Pool
的例子,它維護了一個動態大小的輸出 buffer
。
另外,一些短生命週期的對象不適合使用 pool
來維護,這種情況下使用 pool
不划算。這是應該使用它們自己的 free list
(這裏可能指的是 go 內存模型中用於緩存 <32k小對象的 free list) 更高效。
Pool
一旦使用,不能被複制。
Pool
結構體的定義爲:
type Pool struct {
noCopy noCopy
local unsafe.Pointer // 本地P緩存池指針
localSize uintptr // 本地P緩存池大小
// 當池中沒有可能對象時
// 會調用 New 函數構造構造一個對象
New func() interface{}
}
Pool
中有兩個定義的公共方法,分別是 Put
- 向池中添加元素;Get
- 從池中獲取元素,如果沒有,則調用 New
生成元素,如果 New
未設置,則返回 nil
。
Get
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
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
當世界暫停,垃圾回收將要開始時, 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{}
}
案例1:gin 中的 Context 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)
案例2:fmt 中的 printer pool
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)
}