sync.Pool复用指针对象时不自动重置字段,必须在Put前调用reset方法清空状态(如切片用[:0]、map遍历删除),New函数仅兜底构造不负责初始化,漏reset会导致脏数据污染。

sync.Pool 里存指针对象时,不重置字段会复用脏数据
直接说结论:sync.Pool 不会自动清空或重置你放进去的指针对象,它只管“借”和“还”。如果你往池里 Put 了一个已用过的 *MyStruct,下次 Get 出来时,它的字段值还是上次留下的——不是新对象,是旧对象的“尸体”在诈尸。
常见错误现象:Get 出来的结构体字段值异常(比如 id 是上一个请求的、slice 里混着历史数据、bool 字段为 true 却没被显式设过)。
- 必须在
Put前手动重置字段,或在Get后立即初始化 - 推荐在
Put侧清理:避免每次Get都要判断是否首次使用 - 不要依赖
new(MyStruct)的零值——因为对象是复用的,new只在池空时触发一次
用 New 函数配 reset 方法是最稳的组合
sync.Pool 的 New 字段只在池空时调用,它解决的是“没得可借”的兜底问题,不负责“借出前消毒”。所以标准做法是:把初始化逻辑拆成两部分——New 负责构造原始对象,reset 方法负责每次归还前擦除状态。
示例场景:频繁分配 *bytes.Buffer 或自定义请求上下文结构体。
立即学习“go语言免费学习笔记(深入)”;
-
New返回新对象:New: func() interface{} { return &MyReq{} } - 定义
func (r *MyReq) reset() { r.ID = 0; r.Data = r.Data[:0]; r.Handled = false } -
Put前必须调用:req.reset(); pool.Put(req) - 别在
New里做重置——它不保证每次都被调用
切片字段不清空 [:0] 就等于共享底层数组
这是最隐蔽也最常踩的坑。结构体里有 []byte、[]string 等切片字段时,仅设 nil 不够,因为 nil 和 [:0] 在追加时行为不同;更关键的是,如果不清空,下次 append 可能从旧长度继续写,导致数据污染或越界 panic。
错误写法:r.Data = nil → 底层数组可能还在,GC 不收,且 append(r.Data, x) 会从旧 len 开始写。
- 正确做法:
r.Data = r.Data[:0](保留底层数组,清空逻辑长度) - 如果确定不再需要底层数组,才用
r.Data = nil,但会增加 GC 压力 - map 字段同理:
for k := range r.Map { delete(r.Map, k) },不能只赋nil - 嵌套结构体字段也要逐层 reset,
sync.Pool不递归处理
Go 1.21+ 的 Pool.New 不再支持返回 nil,但 reset 逻辑没变
新版 Go 强制 New 必须返回非 nil 值,否则 panic:sync: New function must not return nil。这其实是好事——堵死了“New 返回 nil 导致 Get 拿到 nil 指针”的空指针漏洞。但注意:这个改动和 reset 完全无关,该漏的数据照漏。
- 升级后若遇到 panic,检查
New函数是否可能返回nil(比如内部 new 失败、条件判断分支) - 仍需坚持 “
Get后不信任、Put前必 reset” 原则 - 测试时建议用
GODEBUG=pooldebug=1观察实际复用次数,确认 reset 是否生效
真正难的不是写 reset,是在所有 Put 路径上不漏掉它——包括 defer、error 分支、提前 return 前。漏一次,就可能让一个脏对象流进下一次请求。










