sync.pool仅在对象创建开销大、生命周期短、状态可重置且高并发频繁复用时才适用;误用(如缓存小对象、带外部资源对象)反而增加gc压力。
sync.pool 不能直接帮你“减少内存申请”,它只是把对象缓存起来复用;用错场景反而增加 gc 压力、拖慢性能。
什么时候该用 sync.Pool?
只在满足以下全部条件时才考虑:
- 对象创建开销大(比如
bytes.Buffer、json.Encoder、自定义结构体含大 slice) - 对象生命周期短,且基本在单个 goroutine 内完成分配→使用→归还
- 对象状态可重置(归还前必须清空字段,否则下次取出可能带着脏数据)
- 并发高、对象频繁创建销毁(比如 HTTP handler 中临时 buffer)
反例:缓存数据库连接、用户 session、带 mutex 的结构体——这些不是“临时对象”,也不适合跨 goroutine 复用。
sync.Pool 的 New 函数怎么写才安全?
New 是兜底工厂函数,只在 pool 空时调用;但它不保证线程安全,也不能依赖外部状态(比如全局 map 或未加锁变量)。常见错误是:
- 在
New里做耗时操作(如打开文件、网络请求)→ 拖慢首次 Get - 返回已初始化但不可复用的对象(如未清零的
sync.Mutex字段) - 返回指针却没处理 nil 情况,导致后续 panic
正确写法示例:
var bufPool = sync.Pool{
New: func() interface{} {
// 不做任何副作用,只 new + 预分配
return &bytes.Buffer{Buf: make([]byte, 0, 1024)}
},
}
注意:每次 Get 后必须手动重置,比如 b := bufPool.Get().(*bytes.Buffer); b.Reset(),否则残留数据会污染下一次使用。
为什么用了 sync.Pool 反而更慢?
三个高频原因:
- 对象太小(比如几个 int 字段的 struct):Go 的内存分配器本身很快,pool 的锁和接口转换开销反而更高
- 归还时机不对:在 goroutine 结束前没
Put,导致对象永远留在 pool 里,既浪费内存又干扰 GC 判断 - 误以为 pool 是全局缓存:多个 goroutine 频繁
Get/Put同一个 pool,触发内部 shard 锁争用(尤其在 Go 1.19 之前)
验证方法:跑 go test -bench . -benchmem -cpuprofile=cpu.pprof,对比启用前后 allocs/op 和 B/op;如果 allocs 没降甚至上升,说明没用对。
真正关键的不是“用不用 sync.Pool”,而是确认对象是否真的符合“临时+可重置+开销大”这三点;很多情况下,直接让 GC 处理更省心——毕竟 Go 的分配器针对小对象做了深度优化,而 sync.Pool 的维护成本容易被低估。











