臨時(shí)對(duì)象池 pool 是啥?
sync.Pool 給了一大段注釋來說明 pool 是啥,我們看看這段都說了些什么。
臨時(shí)對(duì)象池是一些可以分別存儲(chǔ)和取出的臨時(shí)對(duì)象。
池中的對(duì)象會(huì)在沒有任何通知的情況下被移出(釋放或者重新取出使用)。如果 pool 中持有某個(gè)對(duì)象的唯一引用,則該對(duì)象很可能會(huì)被回收。
Pool 在多 goroutine 使用環(huán)境中是安全的。
Pool 是用來緩存已經(jīng)申請(qǐng)了的 目前未使用的 接下來可能會(huì)使用的 內(nèi)存,以此緩解 GC 壓力。使用它可以方便高效的構(gòu)建線程安全的 free list(一種用于動(dòng)態(tài)內(nèi)存申請(qǐng)的數(shù)據(jù)結(jié)構(gòu))。然而,它并不適合所有場(chǎng)景的 free list。
在同一 package 中獨(dú)立運(yùn)行的多個(gè)獨(dú)立線程之間靜默共享一組臨時(shí)元素才是 pool 的合理使用場(chǎng)景。Pool 提供在多個(gè)獨(dú)立 client 之間共享臨時(shí)元素的機(jī)制。
在 fmt 包中有一個(gè)使用 Pool 的例子,它維護(hù)了一個(gè)動(dòng)態(tài)大小的輸出 buffer。
另外,一些短生命周期的對(duì)象不適合使用 pool 來維護(hù),這種情況下使用 pool 不劃算。這是應(yīng)該使用它們自己的 free list(這里可能指的是 go 內(nèi)存模型中用于緩存 32k小對(duì)象的 free list) 更高效。
Pool 一旦使用,不能被復(fù)制。
Pool 結(jié)構(gòu)體的定義為:
type Pool struct {
noCopy noCopy
local unsafe.Pointer // 本地P緩存池指針
localSize uintptr // 本地P緩存池大小
// 當(dāng)池中沒有可能對(duì)象時(shí)
// 會(huì)調(diào)用 New 函數(shù)構(gòu)造構(gòu)造一個(gè)對(duì)象
New func() interface{}
}
Pool 中有兩個(gè)定義的公共方法,分別是 Put - 向池中添加元素;Get - 從池中獲取元素,如果沒有,則調(diào)用 New 生成元素,如果 New 未設(shè)置,則返回 nil。
Get
Pool 會(huì)為每個(gè) P 維護(hù)一個(gè)本地池,P 的本地池分為 私有池 private 和共享池 shared。私有池中的元素只能本地 P 使用,共享池中的元素可能會(huì)被其他 P 偷走,所以使用私有池 private 時(shí)不用加鎖,而使用共享池 shared 時(shí)需加鎖。
Get 會(huì)優(yōu)先查找本地 private,再查找本地 shared,最后查找其他 P 的 shared,如果以上全部沒有可用元素,最后會(huì)調(diào)用 New 函數(shù)獲取新元素。
func (p *Pool) Get() interface{} {
if race.Enabled {
race.Disable()
}
// 獲取本地 P 的 poolLocal 對(duì)象
l := p.pin()
// 先獲取 private 池中的對(duì)象(只有一個(gè))
x := l.private
l.private = nil
runtime_procUnpin()
if x == nil {
// 查找本地 shared 池,
// 本地 shared 可能會(huì)被其他 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))
}
}
// 未找到可用元素,調(diào)用 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))
// 對(duì)應(yīng) 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 優(yōu)先把元素放在 private 池中;如果 private 不為空,則放在 shared 池中。有趣的是,在入池之前,該元素有 1/4 可能被丟掉。
func (p *Pool) Put(x interface{}) {
if x == nil {
return
}
if race.Enabled {
if fastrand()%4 == 0 {
// 隨機(jī)把元素扔掉...
// 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
當(dāng)世界暫停,垃圾回收將要開始時(shí), poolCleanup 會(huì)被調(diào)用。該函數(shù)內(nèi)不能分配內(nèi)存且不能調(diào)用任何運(yùn)行時(shí)函數(shù)。原因:
防止錯(cuò)誤的保留整個(gè) Pool
如果 GC 發(fā)生時(shí),某個(gè) goroutine 正在訪問 l.shared,整個(gè) Pool 將會(huì)保留,下次執(zhí)行時(shí)將會(huì)有雙倍內(nèi)存
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 應(yīng)用中,后臺(tái)在處理用戶的每條請(qǐng)求時(shí)都會(huì)為當(dāng)前請(qǐng)求創(chuàng)建一個(gè)上下文環(huán)境 Context,用于存儲(chǔ)請(qǐng)求信息及相應(yīng)信息等。Context 滿足長生命周期的特點(diǎn),且用戶請(qǐng)求也是屬于并發(fā)環(huán)境,所以對(duì)于線程安全的 Pool 非常適合用來維護(hù) Context 的臨時(shí)對(duì)象池。
Gin 在結(jié)構(gòu)體 Engine 中定義了一個(gè) pool:
type Engine struct {
// ... 省略了其他字段
pool sync.Pool
}
初始化 engine 時(shí)定義了 pool 的 New 函數(shù):
engine.pool.New = func() interface{} {
return engine.allocateContext()
}
// allocateContext
func (engine *Engine) allocateContext() *Context {
// 構(gòu)造新的上下文對(duì)象
return Context{engine: engine}
}
ServeHttp:
// 從 pool 中獲取,并轉(zhuǎn)化為 *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 也符合長生命周期的特點(diǎn),同時(shí)也會(huì)可能會(huì)在多 goroutine 中使用,所以也適合使用 pool 來維護(hù)。
printer 與 它的臨時(shí)對(duì)象池
// pp 用來維護(hù) printer 的狀態(tài)
// 它通過 sync.Pool 來重用,避免申請(qǐng)內(nèi)存
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)
}
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
您可能感興趣的文章:- Go語言中使用 buffered channel 實(shí)現(xiàn)線程安全的 pool
- Go語言學(xué)習(xí)技巧之如何合理使用Pool