Pool结构体
type Pool struct {
noCopy noCopy
local unsafe.Pointer // local fixed-size per-P pool, actual type is [P]poolLocal
localSize uintptr // size of the local array
// New optionally specifies a function to generate
// a value when Get would otherwise return nil.
// It may not be changed concurrently with calls to Get.
New func() interface{}
}
poolLocal:
每个p有不一样的对象,其中private只能提供给p使用,shared可以被其他p进行steal,但是需要加锁
// Local per-P Pool appendix.
type poolLocalInternal struct {
private interface{} // Can be used only by the respective P.
shared []interface{} // Can be used by any P.
Mutex // Protects shared.
}
Get方法
func (p *Pool) Get() interface{} {
...
l := p.pin() // 选到p对应的poolLocal
x := l.private
l.private = nil
runtime_procUnpin()
if x == nil { // private 没有
l.Lock() // 加锁
last := len(l.shared) - 1
if last >= 0 { // 尝试从自己共享的对象取一个
x = l.shared[last]
l.shared = l.shared[:last]
}
l.Unlock()
if x == nil { // 没办法只能去其他p去steal
x = p.getSlow()
}
}
...
if x == nil && p.New != nil {
x = p.New() // 创建一个
}
return x
}
get方法流程:
- p从自己私有对象获取(无锁)
- p从自己共享对象获取(局部锁,一个p)
- p从其他p偷取共享对象(循环上锁解锁,所以slow)
- 没有在生成
Put方法
// Put adds x to the pool.
func (p *Pool) Put(x interface{}) {
if x == nil {
return
}
...
l := p.pin() // 获取p的poollocal对象
if l.private == nil { // 优先设置私有
l.private = x
x = nil
}
runtime_procUnpin()
if x != nil {
l.Lock()
l.shared = append(l.shared, x) // 追加到自己共享区中
l.Unlock()
}
...
}
Put方法流程:
- 尝试加入私有对象中
- 追加到共享对象中
Pin和getSlow
// pin pins the current goroutine to P, disables preemption and returns poolLocal pool for the P.
// Caller must call runtime_procUnpin() when done with the pool.
func (p *Pool) pin() *poolLocal {
pid := runtime_procPin()
// In pinSlow we store to localSize and then to local, here we load in opposite order.
// Since we've disabled preemption, GC cannot happen in between.
// Thus here we must observe local at least as large localSize.
// We can observe a newer/larger local, it is fine (we must observe its zero-initialized-ness).
s := atomic.LoadUintptr(&p.localSize) // load-acquire 设置保证GC无法发生
l := p.local // load-consume
if uintptr(pid) < s { // p已经存在
return indexLocal(l, pid)
}
return p.pinSlow() // p是新的,生成poollocal结构,并且注册在全局对象allPools上,GC时会扫描。
}
func (p *Pool) pinSlow() *poolLocal {
// Retry under the mutex.
// Can not lock the mutex while pinned.
runtime_procUnpin()
allPoolsMu.Lock()
defer allPoolsMu.Unlock()
pid := runtime_procPin()
// poolCleanup won't be called while we are pinned.
s := p.localSize
l := p.local
if uintptr(pid) < s {
return indexLocal(l, pid)
}
if p.local == nil {
allPools = append(allPools, p) // 注册
}
// If GOMAXPROCS changes between GCs, we re-allocate the array and lose the old one.
size := runtime.GOMAXPROCS(0)
local := make([]poolLocal, size)
atomic.StorePointer(&p.local, unsafe.Pointer(&local[0])) // store-release
atomic.StoreUintptr(&p.localSize, uintptr(size)) // store-release
return &local[pid]
}
getslow:
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++ { // local全局pid到poollocal的对象
l := indexLocal(local, (pid+i+1)%int(size))
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
}
GC
GC会把所有poo清空 。。。注意千万别在意对象的生命周期。。
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{}
}
var (
allPoolsMu Mutex
allPools []*Pool
)
func init() {
runtime_registerPoolCleanup(poolCleanup)
}
总结
- pool 使用需要谨慎,优先确认是否有必要pool存储临时对象。使用前应该了解benchmark评估下与直接生成对象的性能开销对比。优先建议通过堆内存逃逸检查下,如果能把临时对象控制在栈中最好。
- 注意千万别在意对象的生命周期,有类似场景应该避开。