Golang中sync.Pool用來提高對象複用機率,減少gc的壓力,減少內存分配,它是線程安全的,常用來存儲並複用臨時對象。
原理
爲了減小併發中鎖的競爭,sync.pool爲每個P(對象cpu線程)分配一個子池子poolLocal,每個poolLocal有private對象和shared共享列表對象,private對象只有對應的P可訪問,無需加鎖, shared共享列表對象可被其它P共享,需要加鎖。
Pool結構體
type Pool struct {
noCopy noCopy //該對象不能被copy使用
local unsafe.Pointer // [p]poolLocal,固定長度
localSize uintptr //本地緩衝池poolLocal的數量
New func() interface{} //用戶自定義的用於生成對象的方法
}
Get函數
作用:從Pool中獲取一個對象,如果獲取不到並且New函數不爲空,則通過New創建一個對象並返回。否則返回nil
func (p *Pool) Get() interface{} {
if race.Enabled {
race.Disable()
}
//獲取當前線程的poolLocal
l := p.pin()
//如果private對象不爲空則直接返回,並將其置爲nil
x := l.private
l.private = nil
runtime_procUnpin()
if x == nil {
//private不存在則加鎖從shared列表中拿
l.Lock()
last := len(l.shared) - 1
if last >= 0 {
x = l.shared[last]
l.shared = l.shared[:last]
}
l.Unlock()
//如果shared對象列表依然沒有的話,則從其它P的poolLocal獲取
if x == nil {
x = p.getSlow()
}
}
if race.Enabled {
race.Enable()
if x != nil {
race.Acquire(poolRaceAddr(x))
}
}
// 如果存在New func回調函數,則執行
if x == nil && p.New != nil {
x = p.New()
}
return x
}
Put()函數
作用:將對象x放入到Pool中,以便複用該對象
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()
}
//如果當前poolLocal的private對象爲nil,則直接賦值
l := p.pin()
if l.private == nil {
l.private = x
x = nil
}
runtime_procUnpin()
//否則加到當前poolLocal的shared列表中
if x != nil {
l.Lock()
l.shared = append(l.shared, x)
l.Unlock()
}
if race.Enabled {
race.Enable()
}
}
poolCleanup回收對象
sync.Pool會在gc時回收pool中的對象
func init() {
runtime_registerPoolCleanup(poolCleanup)
}
func indexLocal(l unsafe.Pointer, i int) *poolLocal {
lp := unsafe.Pointer(uintptr(l) + uintptr(i)*unsafe.Sizeof(poolLocal{}))
return (*poolLocal)(lp)
}
// Implemented in runtime.
func runtime_registerPoolCleanup(cleanup func())
func runtime_procPin() int
func runtime_procUnpin()
func poolCleanup() {
// This function is called with the world stopped, at the beginning of a garbage collection.
// It must not allocate and probably should not call any runtime functions.
// Defensively zero out everything, 2 reasons:
// 1. To prevent false retention of whole Pools.
// 2. If GC happens while a goroutine works with l.shared in Put/Get,
// it will retain whole Pool. So next cycle memory consumption would be doubled.
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{}
}
感謝:http://www.mckee.cn/golang/pkg/syncpool
https://segmentfault.com/a/1190000019973632?utm_source=tag-newest