sync.pool.get可能返回nil是设计使然,不是bug;必须显式判空并兜底构造,不可直接类型断言,否则必panic;new仅兜底调用且须返回全新对象;put前必须reset以避免脏数据和内存泄漏。

sync.Pool.Get 可能返回 nil 是设计使然,不是 bug;不判空就强转类型,必 panic。
Get 后必须显式判空 + 构造兜底
高频调用中,sync.Pool.Get 返回 nil 很常见:GC 清理了本地池、goroutine 迁移导致私有池失效、或对象刚被其他 goroutine 取走。直接 (*MyStruct)(pool.Get()) 会立即 panic。
- 正确写法始终是:
obj := pool.Get(); if obj == nil { obj = &MyStruct{} } - 不要在
if外做类型断言,避免nil强转 -
New函数只在Get返回nil时触发,它不参与复用逻辑,仅兜底 - 若对象构造开销大(如含预分配 map/slice),务必在
New里完成,而不是 Get 后再 append
Put 前必须 Reset 并确认无引用
sync.Pool.Put 不清状态、不校验、不阻塞——它只是把指针“扔进池子”,下次 Get 可能立刻被别的 goroutine 拿走。残留字段 = 脏数据,残留引用 = use-after-free。
- 对
*bytes.Buffer:必须调buf.Reset(),而非buf.Truncate(0)(后者不清 cap 外部引用) - 对 slice 字段:
s.data = s.data[:0],不是s.data = nil(后者丢弃底层数组) - 对 map 字段:Go 1.21+ 用
clear(m);旧版本需for k := range m { delete(m, k) } - 禁止
defer pool.Put(x)无脑套用:函数 panic 或提前 return 时 defer 不执行;若 x 已发到 channel 或传入 goroutine,Put 就是灾难
对象大小与复用频率决定 Pool 是否真有效
不是所有小对象都适合放 sync.Pool;用错反而拉高锁竞争和 GC 扫描负担。
- 理想对象大小:64B–512B;超过 32KB 的对象会被直接丢弃,不入池
- 单 goroutine 每秒
Get/Put超过 10⁵ 次且对象 > 1KB 时,shared 队列锁竞争可能反超 new 开销 - 跨 P Put(如 worker goroutine Put 到 main 创建的 Pool)会进全局 shared 队列,强制加锁
- 用
go tool trace观察runtime.mallocgc和runtime.gcStopTheWorld占比:若 GC 时间降但 CPU time 升,大概率是 false sharing 或锁争用
New 函数必须线程安全且返回全新对象
New 不是初始化钩子,它是兜底工厂;它可能在任意 goroutine 中被调用(比如 GC 后首次 Get),且不保证只调一次。
- 错误:
var globalBuf bytes.Buffer; New: func() interface{} { return &globalBuf }→ 多个 goroutine 共享同一内存,竞态 - 正确:
New: func() interface{} { return &bytes.Buffer{} }或New: func() interface{} { return make([]byte, 0, 1024) } - 禁止在
New里做耗时操作(os.Open、http.Get)、带状态操作(修改全局变量)、或依赖当前 goroutine 上下文 -
New返回的对象仍需在Get后手动Reset;它不替代状态清理
最易被忽略的一点:Pool 复用的是堆对象,不改变逃逸结果。只要 Get 返回值被传给 interface{} 参数、闭包捕获、或发到 channel,它依然逃逸——Pool 只是让逃逸后的对象被复用,而非阻止逃逸。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











