sync.Pool
管理一組可以單獨保存和檢索的臨時對象。目的是緩存已分配但未使用的對象,以供以后重用,從而減輕GC的壓力。核心就是Put
、Get
和New
使用sync.Pool
需要提供一個New
方法,以便在池中沒有緩存的對象時,調(diào)用New
創(chuàng)建
type Pool struct {noCopy noCopy // 靜態(tài)檢查機制:內(nèi)置noCopy結構體的對象在第一次使用后不會再發(fā)生復制
local unsafe.Pointer // local 固定大小 per-P 池, 實際類型為[P]poolLocal
localSize uintptr // local array 的大小
victim unsafe.Pointer // 在上一個GC周期local被poolCleanup函數(shù)放置于此,它可能尚未被清理。 后面再講
victimSize uintptr // victims array 的大小
// 在Get方法失敗的情況下,選擇性的創(chuàng)建一個值
New func() interface{}
}
poolLocal結構體type poolLocalInternal struct {private interface{} // 只能被各自的P使用
shared poolChain // 可以被任意P使用
}
type poolLocal struct {poolLocalInternal
// 對齊到機器的緩存行大小,以避免false sharing [1]
pad [128 - unsafe.Sizeof(poolLocalInternal{})%128]byte
}
private
只保存一個對象,且只能被擁有當前poolLocal
的P
訪問shared
是一個鏈表,可以被其他P
訪問有了上面這張圖,Get
如何從池中獲取對象,也能猜個七七八八了。
P
對應的poolLocal.private
。P
的share
的head
鏈表的頭部取出一個對象。share
的tail
中steal一個對象。New
創(chuàng)建一個對象。func (p *Pool) Get() interface{} {// 如果啟用的 race 檢查則先停用
if race.Enabled {race.Disable()
}
// 返回pid,和 poolLocal
l, pid := p.pin()
// 嘗試從private中獲取數(shù)據(jù)
x := l.private
// 獲取之后將private置nil,相當于從poolLocal中移除對象
l.private = nil
// 若從private中獲取失敗
if x == nil {// 為了更好的利用時間局部性,從 shared 頭部讀取對象
x, _ = l.shared.popHead()
// 如果讀取不到,則steal獲取新的緩存對象
if x == nil { x = p.getSlow(pid)
}
}
runtime_procUnpin()
// 恢復 race 檢查
if race.Enabled {race.Enable()
if x != nil { race.Acquire(poolRaceAddr(x))
}
}
// 若還是取不出來則調(diào)用New 創(chuàng)建
if x == nil && p.New != nil {x = p.New()
}
return x
}
竊取對象竊取的策略
shared
竊取。poolLocal
的private
取對象。poolLocal
的shared
取對象。Get
函數(shù)調(diào)用New
創(chuàng)建一個對象。func (p *Pool) getSlow(pid int) interface{} {// 遍歷所有p的 poolLocal 嘗試從shared中竊取一個對象
size := runtime_LoadAcquintptr(&p.localSize) // load-acquire
locals := p.local // load-consume
// Try to steal one element from other procs.
for i := 0; i< int(size); i++ {l := indexLocal(locals, (pid+i+1)%int(size))
if x, _ := l.shared.popTail(); x != nil { return x
}
}
// 遍歷所有p的victim
size = atomic.LoadUintptr(&p.victimSize)
if uintptr(pid) >= size {return nil
}
// 遍歷當前p的victim的private
locals = p.victim
l := indexLocal(locals, pid)
if x := l.private; x != nil {l.private = nil
return x
}
// 遍歷其他p的victim的shared
for i := 0; i< int(size); i++ {l := indexLocal(locals, (pid+i)%int(size))
if x, _ := l.shared.popTail(); x != nil { return x
}
}
atomic.StoreUintptr(&p.victimSize, 0)
return nil
}
Put將一個(不確定對象狀態(tài))的對象放入到池中,遵循以下策略。
private
private
已經(jīng)有值,則嘗試放入shared
func (p *Pool) Put(x interface{}) {if x == nil { return
}
// 停用 race
if race.Enabled { if fastrand()%4 == 0 { // Randomly drop x on floor.
return
}
race.ReleaseMerge(poolRaceAddr(x))
race.Disable()
}
// 獲取 localPool
l, _ := p.pin()
// 優(yōu)先放入 private
if l.private == nil { l.private = x
x = nil
}
// 如果不能放入 private 則放入 shared
if x != nil { l.shared.pushHead(x)
}
runtime_procUnpin()
// 恢復race
if race.Enabled { race.Enable()
}
}
惰性回收sync.Pool 的垃圾回收發(fā)生在運行時 GC 開始之前。
var poolcleanup func()
// 利用編譯器標志將 sync 包中的清理注冊到運行時
//go:linkname sync_runtime_registerPoolCleanup sync.runtime_registerPoolCleanup
func sync_runtime_registerPoolCleanup(f func()) {poolcleanup = f
}
// 實現(xiàn)緩存清理
func clearpools() {// clear sync.Pools
if poolcleanup != nil {poolcleanup()
}
(...)
}
清理函數(shù)victim
的使用出現(xiàn)在getslow
函數(shù)中,當從其他P
的shared
中無法竊取到對象時,會嘗試從上一次GC周期時放置的緩存中獲取對象。
這就涉及到了惰性回收,當GC觸發(fā)前poolCleanup
會將運行時中所有sync.Pool
對象中的poolLocal
移動到其對應victim
字段,victim
會保存一個GC周期后被清除。
func poolCleanup() {// 清空上一GC周期的victim緩存
for _, p := range oldPools { p.victim = nil
p.victimSize = 0
}
// 將當前運行時中所有Pool(不同的Pool對象)中的local移動到其victim中
for _, p := range allPools { p.victim = p.local
p.victimSize = p.localSize
p.local = nil
p.localSize = 0
}
// 互換oldPool和allPools,并將allPools置nil
oldPools, allPools = allPools, nil
}
備注才疏學淺,若有疑惑之處,很可能是筆者出錯了。還望不吝賜教。
參考資料[1] false sharing
2 歐老師:Go source study: sync.Pool
你是否還在尋找穩(wěn)定的海外服務器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機房具備T級流量清洗系統(tǒng)配攻擊溯源,準確流量調(diào)度確保服務器高可用性,企業(yè)級服務器適合批量采購,新人活動首月15元起,快前往官網(wǎng)查看詳情吧