sync.pool复用的是new函数返回的可安全重置的临时对象实例,而非函数本身;必须在get后手动reset以清除len、cap、err等残留状态,否则导致脏数据;new仅兜底创建干净对象,put前须确保对象未被逃逸且未重复放入。

sync.Pool 不复用“函数”,它复用的是 New 函数返回的对象实例。所谓“复用函数创建的对象”,本质是复用 New 返回的、可安全重置的临时值——不是靠函数逻辑复用,而是靠对象状态清理 + 池管理机制降低分配。
Get 之后不调 Reset 就会出脏数据,不是 bug 是设计使然
sync.Pool 完全不管你的结构体字段含义,它只做两件事:存指针、取指针。底层内存(比如 []byte 的底层数组)被复用,但 len、cap、Err、pos 这些字段不会自动归零。
常见错误现象:
-
json.Unmarshal解析出错,因为上一次用过的map[string]interface{}里还残留旧 key -
bytes.Buffer.String()返回前一次拼接的残留内容 -
http.Request.Header里混着上个请求的 Cookie
必须手动重置的典型操作:
-
buf.Reset()(bytes.Buffer) -
decoder.DisallowUnknownFields()(json.Decoder) -
ctx.Input = ctx.Input[:0](自定义切片字段) -
ctx.Err = nil(错误字段) -
for k := range ctx.cache { delete(ctx.cache, k) }(map 字段)
别信“New 已初始化就不用 Reset”——New 只兜底,Get 返回的极大概率是别人用过的脏对象。
New 函数写错会导致 panic 或循环污染
New 是懒加载工厂,不是构造入口。它只在池空时触发,且可能被多个 goroutine 并发调用。错误写法会直接破坏池行为:
错误示例:
New: func() interface{} {
return pool.Get() // ❌ 绝对禁止!导致循环引用、panic: sync: inconsistent pool behavior
}
正确写法必须满足两个条件:
- 返回全新实例(不能复用池内已有对象)
- 返回对象状态干净(字段全零值,或内部已调用 Reset)
推荐模式:
-
return &MyStruct{}(结构体字段默认零值) -
return bytes.NewBuffer(nil)(标准库已保证 Reset 可用) -
return &ParserCtx{Input: make([]byte, 0, 4096)}(预分配但 len=0)
如果结构体含指针字段(如 mu *sync.RWMutex),New 里不能 new 一个新锁然后复用——下次 Get 拿到的仍是旧锁指针,并发读写直接 panic。
Put 的时机比 Get 更关键,defer 不可靠
defer pool.Put(x) 看起来整洁,但实际风险很高:
- 函数 panic 时 defer 不执行 → 对象永久丢失
- 提前 return(比如校验失败)→ defer 跳过 → 对象泄露
- 对象被逃逸到子 goroutine → Put 时原 goroutine 已结束,对象被非法回收
更稳妥的做法:
- 在明确的业务终点 Put,比如 HTTP handler 的
w.WriteHeader之后、JSON 解析完成之后 - 如果必须 defer,先确保对象没被修改、没逃逸、且函数逻辑无 panic 路径
- 给对象加标记字段(如
inPool bool),Put 前断言!obj.inPool,快速捕获重复 Put
还要注意:重复 Put 同一个对象会 panic,Put(nil) 也会 panic。务必确认对象非 nil 且未被 Put 过。
GC 清空池不是失效,而是设计约束
每次 GC,sync.Pool 会清掉所有未被当前 goroutine 持有的对象。这不是缺陷,是机制:
- 高频 GC 场景(小堆、内存紧张)下,池命中率骤降,收益被抹平
- 长时间不 GC(大堆、低分配率)下,旧对象滞留,但不会泄漏——只是占用一点内存
- 它不提供淘汰策略、不保序、不保量,也不承诺“谁 Put 谁 Get”
验证是否真有效:
- 用
go tool pprof -alloc_space对比开启前后堆分配总量 - 开启调试:
GODEBUG=pooldebug=1查看命中率(输出到 stderr) - 注意:对象大小 >2KB 且每秒 Get/Put 超百万时,池自身开销可达 5%~10% CPU(Go 1.22+ 优化后仍存在)
真正容易被忽略的点:Pool 不是替代栈分配的方案。优先用逃逸分析让小对象留在栈上;只有高频、固定大小、能 Reset 的堆对象才值得进 Pool。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











