sync.Pool仅适用于高频创建、短期存活、结构体大小适中的场景;不适用生命周期长、分配开销低、已自带复用机制或含长期指针的对象,且New必须返回全新对象,Put前须确保无任何goroutine再使用该对象。
sync.Pool 什么时候该用,什么时候别硬套
它不是万能加速器,只在「高频创建+短期存活+结构体大小适中」的场景下才值得引入。比如 HTTP 中间件里反复分配
或自定义请求上下文结构体;但如果你的对象生命周期长(如全局配置缓存)、或单次分配开销极低(如几个 int 字段的 struct),加
反而增加调度开销和内存碎片。
典型适用:每次请求都 new 一个
、
、或含 slice 字段的临时 DTO
明显不适用:持有锁、包含指针指向长期存活对象、或本身已复用(如
自带复用机制)
注意:
不保证对象一定被复用——GC 时会清空,且无 LRU 等淘汰策略,纯靠「最近用过就留着」
New 字段必须返回全新对象,不能返回共享实例
这是最容易踩的坑:
的
函数如果返回了同一个全局变量或复用的底层数组,多个 goroutine 并发 Get/.Put 就会相互污染数据,引发隐蔽的竞态或 panic。
错误写法:
—— 全局变量被所有 goroutine 共享
正确写法:
—— 每次 New 都分配新内存
尤其注意 slice 和 map:不要直接
后不清空内容,Put 前必须重置长度(
),否则下次 Get 到的是残留数据
Put 之前必须确保对象不再被任何 goroutine 使用
不做引用计数,也不检测是否还在被使用。一旦 Put 进去,Pool 可能在任意时刻把它交给别的 goroutine,此时原 goroutine 若继续读写该对象,就是典型的 use-after-free。
常见错误:在 defer 里 Put,但函数返回后仍有 goroutine 持有该对象指针(如启动了异步 callback)
安全做法:Put 前确认对象作用域已结束——通常放在处理逻辑末尾、且无 channel 发送、无闭包捕获、无 goroutine 启动
调试技巧:开启
观察 GC 频次变化;配合
检测潜在竞态
Go 1.19+ 的 Pool 改动影响实际复用率
从 Go 1.19 开始,
的本地池(per-P)清理时机更激进:当 P 被长时间闲置(如大量 goroutine 阻塞),其绑定的 pool 会被提前清空。这意味着高并发短任务(如大量快速 HTTP 请求)复用率可能比旧版本更低。
立即学习
“
go语言免费学习笔记(深入)
”;
表现现象:压测时 GC 次数下降不明显,
显示
改善有限
应对方式:避免过度依赖 Pool 复用,优先考虑栈上分配或对象内联;对关键路径可结合
对比
和
兼容提示:Go 1.21 引入
可为 nil,但行为不变——仍需保证返回值是新对象
真正难的不是写对
和
,而是判断某个对象到底「值不值得放进 Pool」。它要求你同时看清分配频率、对象生命周期、GC 日志里的 pause 分布,以及线上真实流量下的内存 profile —— 这些没法靠模板解决。
*bytes.Buffersync.Pooljson.Decoderbufio.Readerhttp.Requestsync.Poolsync.PoolNewNew: func() interface{} { return &myGlobalBuf }New: func() interface{} { return &MyStruct{Data: make([]byte, 0, 128)} }return make([]int, 0)s = s[:0]sync.Pool.PutGODEBUG=gctrace=1-racesync.Poolruntime.ReadMemStatsPauseNspprofallocsheap_inusePool.NewGetPut