sync.pool不是用来省内存,而是压低gc频率;仅适用于创建频繁、生命周期短、可安全重置的对象,误用会增加锁竞争和脏数据风险。

sync.Pool 不是用来“省内存”的,而是用来压低 GC 频率的——它只在对象创建频次高、生命周期短、能安全重置时才有效;用错场景反而增加锁竞争和脏数据风险。
sync.Pool.Get 返回 nil 怎么办
Get() 本就可能返回 nil,尤其在 Pool 刚初始化、GC 清理后、或跨 P 偷取失败时。这不是 bug,是设计行为。
- 必须用
if buf == nil或类型断言后判空,不能直接 dereference -
New函数只在 Get() 返回nil时触发,且必须每次返回新对象(不能复用全局变量) - 常见错误:把
buf := pool.Get().(*bytes.Buffer)当作非空使用,结果 panic
Put 前不 reset 导致下次 Get 拿到脏数据
Put() 只是把指针放回池里,不会自动清空状态。如果结构体字段没归零,下一次 Get() 拿到的就是残留数据。
-
bytes.Buffer必须调用buf.Reset(),不是buf = nil - 切片类对象要用
s = s[:0]清空长度,保留底层数组;用s = nil或s = make(...)会破坏复用意义 - 含指针字段(如
*sync.Mutex)的结构体,Put 前必须确保已解锁,否则下次 Get 后 Lock() 直接 panic
高并发下 sync.Pool 反而变慢的三个原因
Pool 内部按 P 分片,但实际性能高度依赖 goroutine 调度行为和对象复用密度。
- goroutine 频繁跨 P 迁移(比如大量
runtime.Gosched()或 channel 阻塞唤醒),导致本地 private 缓存命中率暴跌,退化为 shared 队列锁竞争 - 对象尺寸差异大,小请求总从大缓冲池里拿,浪费内存且加剧 false sharing
- New 函数本身有开销(如初始化 map、调用 syscall),若对象创建成本不高,Pool 反而引入额外分支和调度延迟
哪些对象绝对不该放进 sync.Pool
Pool 不是通用缓存,更不是资源管理器。放错对象等于埋雷。
- 持有外部资源的对象:含
io.Reader、http.ResponseWriter、文件句柄、DB 连接等字段的结构体 - 带不可重入状态的对象:比如已注册回调、启动了后台 goroutine、或内部维护未同步的计数器
- 被多个 goroutine 共享或长期持有的对象:Pool 不提供所有权转移语义,Put 后无法保证对象不被其他 goroutine 并发访问
真正难的不是写对 Get 和 Put,而是判断某个对象是否满足“短命、可重置、无副作用”这三条——漏掉任意一条,Pool 就从优化变成隐患。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











