sync.pool仅在高频创建、短期存活、构造开销大且能安全重置时才有效;误用会抬高rss、引发数据竞争和脏数据bug,new函数并发不安全,get后必须手动reset,put前须确保对象不再被任何goroutine使用。

sync.Pool 不是用来“省点内存分配”的通用优化手段,它只在极少数场景下真正有效:高频创建 + 短期存活 + 构造开销大 + 能安全重置。用错比不用更糟,会抬高 RSS、触发数据竞争、掩盖脏数据 bug。
sync.Pool.New 函数不是构造器,是并发不安全的兜底工厂
New 只在 Get() 发现池空时才调用,且会被多个 goroutine 同时调用——它内部不能读写共享变量、不能打日志、不能发请求。
- 错误写法:
New: func() interface{} { return &globalBuf }(返回全局变量地址,多个 goroutine 共享同一对象) - 正确写法:
New: func() interface{} { return new(bytes.Buffer) }或New: func() interface{} { return make([]byte, 0, 1024) } - 若结构体含
map/slice/pointer字段,New中必须预分配并清零,否则Get()拿到的是带残留数据的旧实例
Get 后必须手动 Reset,Go 绝不会帮你清状态
Get() 返回的可能是上次 Put() 进去的旧对象,字段值完全不可控。没 Reset() 就复用,轻则逻辑错乱,重则 panic(比如 bytes.Buffer 底层 []byte 指向已释放内存)。
- 对
bytes.Buffer:拿到后立刻调b.Reset() - 对自定义结构体:必须提供幂等的
Reset()方法,并在Get()后显式调用 - 对
map[string]interface{}:不能只m = make(map[string]interface{}),要for k := range m { delete(m, k) }或复用底层数组 - 对
[]byte:推荐用slice = slice[:0]清空,而非重新make
Put 前必须确保对象不再被任何 goroutine 使用
Put() 是放弃所有权,不是“归还”。一旦放入池,该对象可能被任意 goroutine 下次 Get() 拿走。若此时还有 goroutine 正在读写它,就会引发数据竞争。
- 危险操作:
Put()一个正作为http.Request.Body缓冲的[]byte,而 Body 解析尚未完成 - 安全边界:严格限定作用域,比如只在单个 HTTP handler 内完成
Get → use → Put - 避免提前
Put():不要在函数开头写defer pool.Put(x)—— panic 或提前return会导致漏调,对象永久泄漏 -
race detector能捕获部分问题,但 slice/map 底层数组共享时极难排查
哪些对象根本不该塞进 sync.Pool
误用比不用更糟。以下类型放进池里,基本等于给 GC 和调试加戏。
-
*sql.DB、http.Client、数据库连接:它们本该由专用连接池管理,且持有外部资源 -
string、int、小 struct(如struct{ ID int }):Go 运行时已有高效分配器,池反而增加调度负担 - 大结构体(> 1KB)、含文件句柄/锁/channel 的对象:GC 压力隐性升高,RSS 难以回收,且无法安全重置
- 单次请求中只用一次的对象:池无意义,还拖慢 GC
真正值得池化的,只有像 bytes.Buffer、预分配的 [][]byte、HTTP 请求中反复复用的 http.Header clone、或 JSON 解析用的 map[string]interface{} 实例——前提是每次使用前都彻底重置。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











