一、什么时临时对象池
golang提供了对象重用机制,也就是sync.Pool
pool用来缓存已经申请但未被使用 接下来可能会被使用的内存,以此缓解GC的压力
sync.Pool是并发安全的,可伸缩的,大小仅受限于内存的大小
sync.Pool非常适用于在并发编程中用作临时对象缓存,实现对象的重复使用,优化 GC,提升系统性能,但是由于不能设置对象池大小,而且放进对象池的临时对象每次 GC 运行时会被清除,所以只能用作简单的临时对象池,不能用作持久化的长连接池,比如数据库连接池、Redis 连接池
fmt.Println的实现是使用的sync.Pool
Go语言 sync.Pool 应用详解_sync.pool使用场景-CSDN博客
二、临时对象池的实现
实现对象池需要两个公共方法
Get() 从对象池取一个临时对象,没有的话会New一个
Put() 把对象放回对象池,GC的时候会被清理
1.pool的结构
临时对象池
type Pool struct {
noCopy noCopy
local unsafe.Pointer // [P]poolLocal P的个数一般设置的跟CPU核数相等
localSize uintptr
New func() interface{}
}
禁止拷贝,使用者只能通过指针传递保证全局唯一
go vet可以检测出来
type noCopy struct{}
func (*noCopy) Lock() {}
func (*noCopy) Unlock() {}
type poolLocal struct {
poolLocalInternal
}
type poolLocalInternal struct {
private interface{}
shared []interface{}
sync.Mutex
}
2.取对象
从池中获取元素,如果没有就New一个,如果New为空,则返回nil
Get会为每个P维护一个本地池,P的本地池分为私有池private和共享池shared
私有池中的元素只能本地P使用,共享池中的元素可以被其它P偷走
Get会优先查找本地私有池,在查找本地共享池,最后查找其它P的共享池,如果以上全没有,调用New创建
func (p *Pool) Get() interface{} {
if race.Enabled {
race.Disable()
}
//获取本地的P的poolLocal
l := p.pin()
x := l.private
l.private = nil
runtime_procUnpin()
if x == nil {
//从本地共享池获取poolLocal
//本地shared池可能被其它P访问,需加锁
l.Lock()
last := len(l.shared) - 1
if last >= 0 {
x = l.shared[last]
l.shared = l.shared[:last]
}
l.Unlock()
//从其它P的共享池获取poolLocal
if x == nil {
x = p.getSlow()
}
}
if race.Enabled {
race.Enable()
if x != nil {
race.Acquire(poolRaceAddr(x))
}
}
//没有找到可用对象,new一个新的返回
if x == nil && p.New != nil {
x = p.New()
}
return x
}
3.归还对象
优先放入本地的private池中,如果private不为空,则放入shared池
放入之前有1/4的概率被丢掉
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()
}
l := p.pin()
//如果本地poolLocal的private对象是空的,则优先放入这里
if l.private == nil {
l.private = x
x = nil
}
runtime_procUnpin()
//如果存入private失败,放入本地poolLocal的shared数组中
if x != nil {
l.Lock()
l.shared = append(l.shared, x)
l.Unlock()
}
if race.Enabled {
race.Enable()
}
}
从其它P的共享池获取poolLocal
func (p *Pool) getSlow() (x interface{}) {
size := atomic.LoadUintptr(&p.localSize)
local := p.local
// 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))
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
}
4.对象池清除,GC之前会调用清空
var allPools []*Pool
该函数内不能分配内存且不能调用任何运行时函数
原因: 防止错误的保留整个 Pool
如果 GC 发生时,某个 goroutine 正在访问 l.shared,整个 Pool 将会保留,下次执行时将会有双倍内存
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{}
}
// 清理
func runtime_registerPoolCleanup(func())
// 返回pid,并对m加锁,禁止当前p被抢占
func runtime_procPin() int
// 对m解锁, 把禁止抢占标识释放了
func runtime_procUnpin()
三、参考