Pool 对象池
- 主要适用于大量对象资源被反复构造和回收的场景
- 可以进行一个资源的回收利用、减轻GC压力提高性能
内部数据结构
type Pool struct {
noCopy noCopy
local unsafe.Pointer
localSize uintptr
victim unsafe.Pointer
victimSize uintptr
New func() any
}
nocopy:防止赋值的标识
local:类型为[P]poolLocal的数组,这里的容量P为GOMAXPROCS的长度,就是GMP的P个数
- 所以local的数组结构应该是
[P_index]poolLocal
localSize:local数组长度
victim:存放上一轮被GC回收的local
victimSize:victim数组长度
New:创建对象的函数
local数组的数据结构
type poolLocal struct {
poolLocalInternal
pad [128 - unsafe.Sizeof(poolLocalInternal{})%128]byte
}
type poolLocalInternal struct {
private any
shared poolChain
}
poolLocal:为 Pool 中对应于某个P的缓存数据
poolLocalInternal.private:私有的只能存储一个对象,可无锁化进行访问
poolLocalInternal.shared:本地P的的队列,如果是自己去取队列中的对象,可以保证无锁化访问,但是如果是窃取其他P本地对队列的对象就需要进行加锁操作
核心方法
var (
allPoolsMu Mutex
allPools []*Pool
oldPools []*Pool
)
func (p *Pool) pin() (*poolLocal, int) {
if p == nil {
panic("nil Pool")
}
pid := runtime_procPin()
s := runtime_LoadAcquintptr(&p.localSize)
l := p.local
if uintptr(pid) < s {
return indexLocal(l, pid), pid
}
return p.pinSlow()
}
func (p *Pool) pinSlow() (*poolLocal, int) {
runtime_procUnpin()
allPoolsMu.Lock()
defer allPoolsMu.Unlock()
pid := runtime_procPin()
s := p.localSize
l := p.local
if uintptr(pid) < s {
return indexLocal(l, pid), pid
}
if p.local == nil {
allPools = append(allPools, p)
}
size := runtime.GOMAXPROCS(0)
local := make([]poolLocal, size)
atomic.StorePointer(&p.local, unsafe.Pointer(&local[0]))
runtime_StoreReluintptr(&p.localSize, uintptr(size))
return &local[pid], pid
}
- 调用pin方法之后会先将当前goroutine与P进行绑定,并且让当前的goroutine进入一个不可以被抢占的状态。
- 然后进行获取当前pool中local数组和数组长度,然后进行判断
- 如果输入长度大于当前P的index说明存在直接返回
- 但是如果是第一次调用pin方法的情况下,就要进行一个初始化操作
- 因为之前的绑定操作,所以要先进行一个解绑。加锁,进行一个再次确认
- double check还是没有初始化的情况下,就进行开始初始化local数组和lcoalSize
- 然后进行一个返回
Put
func (p *Pool) Put(x any) {
if x == nil {
return
}
l, _ := p.pin()
if l.private == nil {
l.private = x
} else {
l.shared.pushHead(x)
}
runtime_procUnpin()
}
Get
func (p *Pool) Get() any {
l, pid := p.pin()
x := l.private
l.private = nil
if x == nil {
x, _ = l.shared.popHead()
if x == nil {
x = p.getSlow(pid)
}
}
runtime_procUnpin()
if x == nil && p.New != nil {
x = p.New()
}
return x
}
func (p *Pool) getSlow(pid int) any {
size := runtime_LoadAcquintptr(&p.localSize)
locals := p.local
for i := 0; i < int(size); i++ {
l := indexLocal(locals, (pid+i+1)%int(size))
if x, _ := l.shared.popTail(); x != nil {
return x
}
}
size = atomic.LoadUintptr(&p.victimSize)
if uintptr(pid) >= size {
return nil
}
locals = p.victim
l := indexLocal(locals, pid)
if x := l.private; x != nil {
l.private = nil
return x
}
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
}
poolCleanup
- 在下一轮GC开始的时候执行
poolCleanup函数
func poolCleanup() {
for _, p := range oldPools {
p.victim = nil
p.victimSize = 0
}
for _, p := range allPools {
p.victim = p.local
p.victimSize = p.localSize
p.local = nil
p.localSize = 0
}
oldPools, allPools = allPools, nil
}
- 这个就很好理解,在被GC之前先将当前
allPools备份到oldPools中
- 就是有点缓存的感觉和备份的感觉,双重保障吧
tips
- 绑定P的作用在于:需要P所提供的“竞争隔离域”和“CPU”缓存亲和性