sync.Pool.Get() 返回对象后必须手动重置状态,因其可能为之前用过的“脏”实例;New 仅兜底调用,不保证干净值;不重置将导致数据错乱、panic 或内存泄漏。

sync.Pool.Get() 返回对象后必须手动重置状态
Get 拿到的不一定是新对象,很可能是之前用过的“脏”实例。New 字段只在池空时兜底调用,无法保证每次返回干净值。不重置就直接用,轻则字段残留、数据错乱,重则 panic 或内存泄漏。
-
bytes.Buffer必须调用b.Reset(),不能只靠len(b.Bytes()) == 0判断是否为空 - 自定义结构体如
RequestData,要显式清空切片长度(body = body[:0])、重置 map(header = make(map[string]string))、归零数值字段 - 避免在
New函数里复用外部变量(比如闭包捕获的buf),否则所有 Get 都拿到同一个底层数组 - 如果结构体有指针字段(如
*http.Request),重置时需检查是否为 nil,否则req.Header = nil后再make(http.Header)
Put 前没清理干净会导致后续 Get 拿到脏数据
Put 是“交还钥匙”,不是“提交快照”。只要对象还被其他 goroutine 引用,或者内部字段没清空,下次 Get 就可能读到上一次遗留的数据——这比内存泄漏更隐蔽,压测时才暴露。
- HTTP handler 中复用
*http.Request,Put 前必须重置req.URL、req.Body(设为nil或替换为http.NoBody)、req.Header(用Clone()或重新make) - 切片类字段不能只
make([]byte, 0, cap),要slice = slice[:0];否则append可能复用旧底层数组,导致越界写 - map 字段不能只
clear(m)(Go 1.21+ 才支持),老版本必须m = make(map[string]string)再赋值 - 绝不能 Put 已被
goroutine启动后长期持有的对象——比如传给异步任务但没等它结束就 Put
Reset 方法设计不当会掩盖竞争问题
把重置逻辑封装成 Reset() 方法看似整洁,但如果方法内部没处理好并发安全,或 Reset 调用时机不对(比如在 Put 之后又调用),反而让 bug 更难定位。
- Reset 方法内不要启动新 goroutine、不要操作共享 channel、不要调用阻塞 I/O
- Reset 不应依赖外部状态(如从 context 读配置),否则 Get-Put 循环中行为不一致
- Reset 后建议加断言验证关键字段:如
if len(buf.Bytes()) != 0 { panic("buffer not reset") }(仅调试期) - 不要在
New函数里调用Reset()——New 本该返回全新实例,Reset 是 Get 后的事
类型断言失败 + nil 检查漏掉是 runtime panic 的常见源头
Pool 存的是 interface{},Get 后不做类型断言和 nil 判空,直接解引用,运行时立刻 panic。而这个 panic 往往发生在业务逻辑深处,堆栈看不出和 Pool 有关。
立即学习“go语言免费学习笔记(深入)”;
- 永远用两值断言:
v, ok := pool.Get().(*MyStruct); if !ok { v = &MyStruct{} } - 即使设了
New,也不能省略判空——GC 清空后第一次 Get 可能触发 New,但 New 若 panic 或返回 nil,Get 就返回 nil - 别用
pool.Get().(*MyStruct)强转,一旦类型不符或 nil,直接 crash - 如果结构体带大量字段,Reset 逻辑复杂,建议在 Get 后立即调用
v.Reset(),而不是等到业务中间某处才想起来
sync.Pool 的强制前置动作;而 Reset 的边界——在哪清、清多深、谁负责清——恰恰是多数人写完代码就跑,压测时才发现的问题根源。



















