sync.pool 只对“高频创建 + 短生命周期 + 结构体大小适中”的临时对象有效,如 requestcontext、logentry、bytes.buffer;需手动 reset 再 put,避免脏数据;不适用于长周期 goroutine 或带资源初始化的 new 函数。

sync.Pool 在什么场景下真正有用
它只对“高频创建 + 短生命周期 + 结构体大小适中”的临时对象有效。比如 HTTP 中间件里反复构造的 RequestContext、日志模块里的 LogEntry、JSON 序列化用的 bytes.Buffer。如果对象带指针字段太多、或生命周期跨 goroutine、或分配频率很低,用 sync.Pool 反而增加 GC 压力和锁开销。
常见误用:把含未清零字段的结构体直接放进去复用——下次 Get() 出来时字段还是上次的脏值,导致逻辑错乱或数据泄露。
必须手动重置对象字段,不能依赖零值
sync.Pool 不会自动清零字段,Get() 返回的对象可能是任意历史版本。你得在 New 函数里初始化,更关键的是每次 Put() 前必须显式重置可变字段。
- 错误写法:
pool.Put(&MyStruct{Field: "cached"})—— 字段没清空,下次 Get() 就拿到残留值 - 正确写法:定义一个
Reset()方法,在 Put 前调用:obj.Reset(); pool.Put(obj) - 如果结构体字段少,也可以在
New里返回新实例,但 Put 时仍需清空再放回,否则池里堆积脏对象
示例:
var bufPool = sync.Pool{
New: func() interface{} { return new(bytes.Buffer) },
}
// 使用后必须重置
buf := bufPool.Get().(*bytes.Buffer)
buf.Reset() // 必须调用,否则下次 Get() 里还带着旧内容
// ... use buf ...
bufPool.Put(buf)
避免在长生命周期 goroutine 中独占 Pool 实例
每个 P(Go 的调度单位)维护自己的本地池,但本地池会在 GC 时被整体清空。如果某个 goroutine 长时间运行(比如后台监控协程),它从 Pool 获取的对象不会被其他 goroutine 复用,等于白占内存。
更严重的是:若该 goroutine 持有大量 Put 过的对象,且不触发 GC,本地池可能膨胀;而其他高并发短任务 goroutine 却拿不到干净对象。
- 不要在定时器 goroutine 或常驻 worker 里反复 Get/Put 同一批对象
- 高频路径(如 HTTP handler)适合用;低频或长周期任务不如直接 new
- 可通过
GODEBUG=gctrace=1观察 GC 日志里pool allocs和pool sweeps是否匹配,判断复用是否生效
Pool 不是万能缓存,别把它当对象池通用解法
它没有淘汰策略、不支持超时、不保证数量上限,也不提供 Get 失败回调。一旦业务需要按 key 缓存、带 TTL、或强一致性,就得换 map + sync.RWMutex 或专用缓存库。
容易忽略的一点:Pool 的 New 函数在 Get() 无可用对象时才调用,但它不是线程安全的——多个 goroutine 同时触发 New,可能创建多个实例,然后都 Put 进去。只要 New 本身是幂等的(比如只 new 结构体),就没问题;但如果里面做了资源申请(如打开文件),就会出错。
所以 New 函数里只做内存分配,所有初始化逻辑移到 Reset() 或使用前的手动设置里。











