sync.pool 的核心是减少高频临时对象的堆分配以降低 gc 压力,而非提升复用率;适用对象需满足创建开销大、生命周期极短、结构稳定可重置三条件,且 new 必须返回干净实例,put/get 需严格配合状态清理与所有权管理。

Go 语言中 sync.Pool 不是用来“提升复用率”的工具,而是通过**减少高频临时对象的堆分配**来降低 GC 压力、提升吞吐。它不保证复用,也不追求复用率——真正起作用的是“避免 new + 减少逃逸 + 及时归还”,核心目标是让程序更轻、更快、更稳。
适合放进 Pool 的对象长什么样
不是所有对象都值得池化。真正受益的只有三类:
-
创建开销大:比如
bytes.Buffer(底层要 malloc 切片)、json.Decoder(内部状态初始化较重)、或含预分配大 slice 的结构体 - 生命周期极短:只在单次 HTTP 请求、一次日志写入、一段 JSON 解析中使用,结束后不再引用
-
结构稳定、可安全重置:字段不含外部指针(如未关闭的文件句柄)、无 finalizer、无未同步的 mutex;且每次 Get 后能通过
Reset()或手动清空恢复干净状态
New 字段必须设,且只能返回干净新实例
New 不是可选项,是安全底线。不设会导致 Get() 在池空时返回 nil,后续 panic 往往发生在业务深处,极难定位。
正确写法示例:
New: func() interface{} { return bytes.NewBuffer(nil) },
}
错误写法包括:
-
New: nil(直接 panic) -
New: func() interface{} { return bufPool.Get() }(递归调用,栈溢出) -
New: func() interface{} { return &myStruct{data: globalSlice} }(复用外部变量,状态污染)
Put 和 Get 的时机与状态清理比“放回去”更重要
很多人以为“Get → 用 → Put”就完事了,其实关键在中间两步:
-
Get 后必须检查并重置:即使
New返回了新对象,Get也可能返回之前用过的脏实例。例如结构体字段未归零、切片长度非零、map 未清空,都会导致数据残留或内存泄漏 -
Put 前必须确保对象已彻底脱离使用:不能在
defer中无脑 Put——若函数 panic 或提前 return,defer 不执行,对象就丢了;更危险的是,若对象被闭包捕获或传给了其他 goroutine,再 Put 就会引发状态混乱甚至崩溃 - Put 应该尽早,但必须在所有权明确结束之后:比如 HTTP handler 中,应在响应写出完成、所有子 goroutine 已退出、上下文已释放后 Put,而不是在函数入口 defer
别把 Pool 当缓存,也别指望它解决根本问题
sync.Pool 是 per-P 局部缓存 + GC 清理机制,不是全局共享存储。它的对象可能在任意 GC 周期被全量回收,也不保证谁 Put 谁 Get。
如果压测发现 GC 时间没降反升,大概率是误用了:
- 把大对象(>2KB)塞进去——Go 运行时会绕过 Pool 直接走 mcentral 分配
- 放入带 long-lived 指针的结构体(如缓存了 request.Context 或 db.Conn)——造成隐式内存泄漏
- 当成连接池或配置中心用——Pool 不管理生命周期,也不做健康检查
- 忽略逃逸分析:先用
go build -gcflags="-m"确认对象是否真逃逸到堆上;能留在栈上的,比任何 Pool 都快
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











