必须传指针,否则对象无法复用;Get 返回 interface{},需断言为 *T;混存类型或未清空字段会导致脏数据、内存泄漏或 panic。

sync.Pool.Put 传入指针还是值?
必须传指针,否则对象无法复用。因为 sync.Pool 存的是 interface{},而 Go 的 interface 底层包含类型信息和数据指针;如果 Put 的是结构体值,每次 Get 都会得到一个新拷贝,Pool 就失去意义。
- 错误写法:
pool.Put(MyStruct{})—— 每次 Get 返回的都是新分配内存,旧对象被 GC 回收 - 正确写法:
pool.Put(&MyStruct{})或更常见:pool.Put(obj)(obj是已分配的*MyStruct) - 注意:Put 前确保该指针指向的对象不再被其他 goroutine 使用,否则可能引发竞态或脏读
Get 返回的是 *T 还是 T?
Get 返回 interface{},需要类型断言转成具体指针类型。它不自动解引用,也不做类型转换 —— 断言失败会 panic,且不会报编译错误。
- 典型误用:
v := pool.Get().(MyStruct)—— 错!这是值类型断言,但 Pool 里存的是*MyStruct - 正确写法:
v := pool.Get().(*MyStruct),然后记得清空字段(如v.Field = 0),否则可能残留上次使用状态 - 安全做法:加判断避免 panic:
if v, ok := pool.Get().(*MyStruct); ok { ... }
为什么 interface{} 会让 Pool 行为变得“不可见”?
因为 interface{} 抹去了原始类型信息,GC 不知道你存的是指针还是值,Pool 本身也不校验。这导致两个隐蔽问题:
- 混存不同类型:
pool.Put(&MyStruct{})和pool.Put("hello")都能成功,但取出来时类型断言必然失败 - 内存泄漏风险:如果 Put 的指针指向大对象(比如含切片、map 的结构体),而 Get 后没重置内部引用,旧数据可能长期驻留,拖慢 GC
- 调试困难:
runtime.ReadMemStats看不到 Pool 中对象数量,只能靠 pprof + 手动打点观察复用率
实际项目中怎么避免踩坑?
别裸用 sync.Pool,封装一层带类型约束的 wrapper,强制约束 Put/Get 的类型一致性。
- 用泛型(Go 1.18+)封装:
type ObjectPool[T any] struct { pool sync.Pool },在New时绑定构造函数,Get返回*T - 禁止直接暴露
sync.Pool的Put/Get方法,所有存取走封装方法 - 在 Get 返回的对象上加标记字段(如
used bool),配合单元测试验证是否被重复初始化或遗漏重置
最麻烦的地方不是语法,而是「谁负责清理字段」——Pool 不管这个,得你自己在 Get 后立刻初始化,或在 Put 前手动归零。漏一次,就可能在线上跑出难以复现的脏数据。