



















sync.Pool 是 Go 标准库提供的临时对象池,用于存储和复用临时对象,减少内存分配和 GC 压力。
type Pool struct {
noCopy noCopy
local unsafe.Pointer // 本地池
localSize uintptr // 本地池大小
victim unsafe.Pointer // 上一轮 GC 幸存的对象
victimSize uintptr
New func() interface{} // 池为空时创建新对象的函数
}
核心特征:
interface{},使用时需断言// 无池化:高频分配
for i := 0; i < 1000000; i++ {
buf := make([]byte, 1024) // 每次都分配内存
process(buf)
}
// GC 频繁触发,STW 时间增加
| 指标 | 无池化 | 有池化 | 提升 |
|---|---|---|---|
| 内存分配次数 | 100万次 | ~几百次 | 99.9%↓ |
| GC 扫描对象数 | 100万个 | ~100个 | 99.99%↓ |
| 吞吐量 | 基准 | 3-10倍 | 显著 |
✅ 适合:
bytes.Buffer、JSON 编解码器)❌ 不适合:
type poolLocal struct {
poolLocalInternal
pad [128 - unsafe.Sizeof(poolLocalInternal{})%128]byte // 缓存行填充
}
type poolLocalInternal struct {
private interface{} // 仅当前 P 使用,无锁
shared poolChain // 其他 P 可以窃取
}
设计亮点:
private,操作零开销shared 是双向链表,支持其他 P 偷取pad 填充到 128 字节(缓存行大小)func (p *Pool) Get() interface{} {
// 1. 获取当前 P 的本地池
l, pid := p.pin()
// 2. 优先取 private
x := l.private
l.private = nil
if x == nil {
// 3. private 为空,从 shared 队列取
x, _ = l.shared.popHead()
if x == nil {
x = p.getSlow(pid) // 4. 从其他 P 窃取或 victim
}
}
runtime_procUnpin()
// 5. 仍为空则调用 New
if x == nil && p.New != nil {
x = p.New()
}
return x
}
关键机制:
func (p *Pool) Put(x interface{}) {
if x == nil { return }
l, _ := p.pin()
if l.private == nil {
l.private = x // 优先放 private
x = nil
}
if x != nil {
l.shared.pushHead(x) // private 已占用,放 shared
}
runtime_procUnpin()
}
// 每次 GC 时调用
func poolCleanup() {
for _, p := range oldPools {
p.victim = p.local
p.victimSize = p.localSize
p.local = nil
p.localSize = 0
}
// 交换新旧池列表
}
两轮淘汰:
local → victim,local 清空victim 完全清空这样设计避免 GC 后瞬间大量对象重建,提供缓冲期。
type BufferPool struct {
pool sync.Pool
}
func NewBufferPool() *BufferPool {
return &BufferPool{
pool: sync.Pool{
New: func() interface{} {
return bytes.NewBuffer(make([]byte, 0, 4096))
},
},
}
}
func (bp *BufferPool) Get() *bytes.Buffer {
return bp.pool.Get().(*bytes.Buffer)
}
func (bp *BufferPool) Put(buf *bytes.Buffer) {
buf.Reset() // 必须重置!
bp.pool.Put(buf)
}
⚠️ 必须 Reset 对象
// 错误:不重置就放回
pool.Put(buf) // buf 可能残留数据
// 正确:重置后再放回
buf.Reset()
pool.Put(buf)
⚠️ 不要假设对象存在
// 错误:对象可能被 GC 清空
buf := pool.Get().(*bytes.Buffer) // 可能 panic
// 正确:检查 nil
buf := pool.Get()
if buf == nil {
buf = bytes.NewBuffer(...)
}
buffer := buf.(*bytes.Buffer)
⚠️ 不要存储引用类型的外部资源
// 错误:连接池不应使用 sync.Pool
type Conn struct {
net.Conn // 如果放回,可能被 GC 关闭
}
// 正确:只用于纯内存对象
type CacheItem struct {
key string
value []byte
}
GODEBUG=gctrace=1func BenchmarkWithoutPool(b *testing.B) {
for i := 0; i < b.N; i++ {
buf := bytes.NewBuffer(make([]byte, 1024))
buf.WriteString("test")
_ = buf.String()
}
}
func BenchmarkWithPool(b *testing.B) {
pool := sync.Pool{
New: func() interface{} {
return bytes.NewBuffer(make([]byte, 1024))
},
}
for i := 0; i < b.N; i++ {
buf := pool.Get().(*bytes.Buffer)
buf.WriteString("test")
_ = buf.String()
buf.Reset()
pool.Put(buf)
}
}
| 维度 | 结论 |
|---|---|
| 本质 | 并发安全的临时对象缓存,利用 per-P 设计减少锁竞争 |
| 优势 | 大幅降低内存分配和 GC 开销,吞吐量提升数倍 |
| 代价 | 对象会被自动清理,不保证对象存活,需要类型断言 |
| 适用 | 高频创建的临时对象(Buffer、Encoder、复杂结构体) |
| 关键 | Put 前必须 Reset,Get 后必须判空 |
核心思想:用空间换时间,用复用换性能。sync.Pool 不是万能药,但在合适的场景下,它是 Go 性能优化的利器。理解其 victim cache 和工作窃取机制,才能在生产环境中正确使用,避免踩坑。
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。