sync.Pool仅适用于短期、无状态、可随意丢弃的临时对象;不保证复用与回收时机,误用会导致资源泄漏或panic。
为什么 sync.Pool 不是万能的对象复用方案
直接说结论:sync.Pool 适合缓存**短期、无状态、可被随意丢弃**的临时对象(比如切片、小结构体),但不适合管理有外部依赖、需显式清理或带生命周期语义的对象。它不保证对象一定被复用,也不保证回收时机——GC 触发前可能被清空,甚至在 GC 期间被批量销毁。
常见误用场景包括:往 sync.Pool 里塞含文件句柄、数据库连接、未关闭的 http.Response.Body 的对象,结果出现资源泄漏或 panic。
-
sync.Pool的New函数只在 Get 无可用对象时调用,不是每次 Get 都新建 - Pool 中的对象可能在任意一次 GC 后消失,不能假设“Put 进去就还在”
- 多个 goroutine 并发使用同一个 Pool 是安全的,但 Pool 本身不解决对象内部的并发竞争
如何正确初始化并复用一个 bytes.Buffer 池
bytes.Buffer 是 sync.Pool 最典型且安全的使用案例:它无外部资源、可重置、复用成本远低于反复 make([]byte, 0, N)。
var bufferPool = &sync.Pool{
New: func() interface{} {
return new(bytes.Buffer)
},
}
func writeWithBuffer(data []byte) []byte {
b := bufferPool.Get().(*bytes.Buffer)
b.Reset() // 必须重置,否则残留之前内容
b.Write(data)
result := append([]byte(nil), b.Bytes()...)
bufferPool.Put(b)
return result
}
注意三点:
- 必须调用
b.Reset(),否则b.String()或b.Bytes()可能返回旧数据 - 不要把
b.Bytes()返回的切片直接保存或长期持有——底层底层数组仍归属bytes.Buffer,Put 后可能被其他 goroutine 覆盖 - 如果写入量极大且长度波动大,可考虑在
New中预分配容量:&bytes.Buffer{Buf: make([]byte, 0, 1024)}
自定义对象池时如何避免内存泄漏和类型断言 panic
当你需要池化自定义结构体(比如 type RequestCtx struct { ID string; Data map[string]interface{} }),关键在于:对象重置逻辑必须彻底,且 Get/Put 类型必须严格一致。