Go-sync-Pool对象池最佳实践与源码分析
文章导语
sync.Pool是Go性能优化的利器,能显著降低GC压力。但它的"池中对象随时可能被清理"特性让很多开发者困惑。本文深入Pool的源码实现和最佳使用模式。
一、Pool的底层结构
go
type Pool struct {
noCopy noCopy
local unsafe.Pointer // [P]poolLocal
localSize uintptr
victim unsafe.Pointer // 上一轮的local
victimSize uintptr
New func() interface{}
}
type poolLocal struct {
poolLocalInternal
pad [128 - unsafe.Sizeof(poolLocalInternal{})%128]byte // 防止伪共享
}
type poolLocalInternal struct {
private interface{} // 仅当前P访问,无锁
shared poolChain // 当前P pushHead/popHead,其他P popTail
}
二、GC与对象清理
go
// GC时poolCleanup被调用
// 将local移到victim,清空local
// 下次GC前:先从victim获取
// 下次GC时:清空victim
关键理解:Pool中的对象最多存活两轮GC。第一轮从local移到victim,第二轮清空victim。
三、使用最佳实践
go
// 1. Put前重置对象
buf := pool.Get().(*bytes.Buffer)
defer pool.Put(buf)
buf.Reset() // 必须在Put前重置!
// 2. 不要假设Get返回的对象已初始化
func GetBuf() *bytes.Buffer {
buf := bufPool.Get().(*bytes.Buffer)
return buf // 可能包含上次的数据!
}
// 3. Pool适合临时对象,不适合持久化对象
// 错误:将DB连接放Pool
// 正确:连接池用专门的连接池库
四、全文总结
- 私有对象(private)无锁访问,速度最快
- shared队列支持跨P窃取
- 对象在两轮GC后一定被清理
- Put前必须Reset,Get后必须初始化
参考文献
- Go源码 sync/pool.go
- Go Blog - Using sync.Pool
- Dave Cheney - sync.Pool