sync.pool.get 返回的是旧实例,必须同 goroutine 内 reset 后才能安全使用;put 前需确保对象彻底用完且无共享,new 函数须返回全新无状态对象并避免副作用。

sync.Pool.Get 返回的对象必须在同 goroutine 内 Reset 后才能用
Get 拿到的不是新对象,而是上一次 Put 进去的“旧实例”,字段、底层数组、map 等都可能残留脏数据。Go 不会自动帮你清理,也不会检查你是否重置——它只管扔进去、拿出来。
常见错误是拿到 bytes.Buffer 后直接写入,结果前一个请求的响应体还留在 buf.Bytes() 里;或者对自定义结构体只调了 new(MyStruct),但没清空 Items []Item 或 Metadata map[string]string。
-
bytes.Buffer:必须立刻调buf.Reset(),不能只靠New返回新实例 - 切片字段(如
[]byte):用s = s[:0]截断长度,不是s = nil - map 字段:用
clear(m)(Go 1.21+),或遍历delete,避免m = make(map[string]string)导致逃逸 - 含
sync.RWMutex的结构体:Put 前必须重建锁,*o.mu = sync.RWMutex{}不合法,得用o.mu = sync.RWMutex{}
Put 必须在对象彻底用完后、且仅由当前 goroutine 调用
Put 不是“归还”,是“放弃所有权”。一旦调用,该对象可能下一毫秒就被其他 goroutine 的 Get 拿走。此时若还有协程在读写它,就会触发数据竞争或 panic。
典型误用场景包括:把正在被 json.Unmarshal 写入的结构体提前 Put;在 defer 里无条件 pool.Put(x),但 x 已传给后台 goroutine 或闭包捕获;或者 Put 后继续访问字段(比如打印日志)。
- HTTP handler 中,
Put必须放在所有逻辑结束之后,不能放在函数开头或中间 - 用于
io.Copy(dst, src)的 writer,Put必须等io.Copy返回后手动调用 - 对象若作为返回值传出(如
return obj),绝对不能Put - 禁止对同一对象多次
Put,尤其在defer里未加判断,容易 panic:“putting an object into a pool twice”
sync.Pool.New 函数只能兜底构造,不能有副作用
New 只在池空时被调用,且不保证线程安全——它会被多个 goroutine 并发执行。它的唯一职责是:返回一个干净、全新、无共享状态的对象。
错在这里会导致全局污染或阻塞:New 返回全局变量地址(如 &globalBuf),多个 goroutine 共享同一内存;在里面做 http.Get 或打日志;或者预分配超大容量(如 make([]byte, 0, 1MB))导致 GC 压力陡增。
- 始终将
sync.Pool定义为包级变量,例如:var bufferPool = sync.Pool{New: func() interface{} { return &bytes.Buffer{} }} - 需要预分配容量的,必须在
New里完成:return &bytes.Buffer{Buf: make([]byte, 0, 64*1024)} - 结构体字段要显式初始化:
&MyStruct{Data: make([]byte, 0, 128), Cache: make(map[string]int)},别依赖零值 - 禁止返回已存在的指针、禁止调用任何阻塞或带状态操作
goroutine 跨 P 迁移会大幅降低 sync.Pool 复用率
sync.Pool 按 P(processor)分片,每个 P 维护自己的本地私有池。goroutine 在阻塞(如 channel 等待、time.Sleep、系统调用)后被唤醒,可能调度到另一个 P 上——这时它之前 Put 过的对象就不可见了,只能新建或从全局共享池取,性能退化为锁竞争。
这不是 bug,是设计使然。实测中,高并发下若 runtime.ReadMemStats().Mallocs - Frees 长期不收敛,说明对象在本地池囤积却无法被复用,GC 也清不掉(因为还在本地 P 私有池里)。
- 适合场景:单次请求生命周期内能完成
Get → use → Put,比如 HTTP handler、中间件、JSON 解析器 - 不适合场景:对象需跨 goroutine 生命周期传递、长期驻留、或频繁 channel 通信后仍需复用
- 观察指标:用
pprof查sync.Pool.get_slow和sync.Pool.put_slow,飙升说明本地池失效严重 - 若发现复用率低,优先检查是否 goroutine 频繁阻塞/唤醒,而不是盲目加 Pool
真正难的不是调用 Get 和 Put,而是确认对象在每次使用前已被彻底重置、在每次释放前已完全脱离所有作用域——这两点漏掉任何一个,sync.Pool 就从优化工具变成隐蔽 bug 发射器。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











