sync.pool最适合复用生命周期短、创建开销大且能安全重置的临时对象,如[]byte、bytes.buffer、json.decoder及带reset()方法的自定义结构体;禁止复用含外部状态、未同步字段、闭包或含finalizer的对象。

sync.Pool 适合复用什么类型的对象
不是所有对象都值得放进 sync.Pool。它最适合生命周期短、创建开销大、且能安全复用的临时对象,比如 []byte 缓冲区、JSON 解析器、http 中间件上下文结构体、自定义的可重置结构体等。避免放入含外部状态(如已打开的文件句柄、未关闭的数据库连接)或带指针指向长期存活数据的对象——Pool 不保证对象何时被回收,也不做线程安全的引用计数。
常见误用:把带 sync.Mutex 字段但没重置的结构体直接丢进 Pool;或者复用后未清空字段,导致下次取出时携带脏数据。
- 推荐复用:
[]byte、bytes.Buffer、json.Decoder(需调用UseNumber()和重置输入)、自定义结构体(必须提供Reset()方法) - 禁止复用:含未同步字段的结构体、闭包、goroutine 本地变量(除非明确逃逸分析可控)、含 finalizer 的对象
必须实现 Reset() 方法才能安全复用结构体
go 官方不强制要求结构体有 Reset(),但如果你把自定义结构体放进 sync.Pool,又没手动清理字段,下一次 Get() 取出的对象极大概率残留旧值——这比每次都 new 还危险。标准库中 bytes.Buffer 和 sync.Pool 自身的示例都显式调用了 Reset() 或等效逻辑。
例如:
立即学习“go语言免费学习笔记(深入)”;
type Parser struct { data []byte err error pos int } func (p *Parser) Reset() { p.data = p.data[:0] p.err = nil p.pos = 0 } var parserPool = sync.Pool{ New: func() interface{} { return &Parser{} }, }
注意:New 函数返回的是新对象,但 Get() 返回的可能是之前用过的,所以每次使用前必须调用 p.Reset(),不能依赖构造函数初始化。
Put() 前是否要清空字段,取决于对象是否会被其他 goroutine 复用
sync.Pool 是 per-P 池(每个 P 维护一个本地缓存),Put 进去的对象不一定由当前 goroutine 创建,也可能被其他 P 上的 goroutine Get 到。因此 Put 前要不要清空,得看字段是否可能泄露敏感信息或破坏后续使用者逻辑。
- 如果字段是切片(
[]byte)、map、channel 等引用类型:建议置为nil或截断(如s = s[:0]),防止内存泄漏或意外读写 - 如果字段是基本类型(
int、bool)或指针:只要Reset()已覆盖,Put 前无需额外操作 - 不要在
Put()里释放资源(如close(ch)):因为对象可能被再次Get(),此时 channel 已关闭会 panic
sync.Pool 不是万能的,过度使用反而降低性能
Pool 本身有锁和原子操作开销,小对象(如几个字节的结构体)反复 Get/Put,性能可能不如直接分配。实测表明,只有当对象分配成本显著高于 Pool 查找+重置成本时,收益才明显。典型分水岭是对象大小 ≥ 128 字节,或构造过程涉及反射、堆分配、系统调用等。
另外要注意 GC 行为:sync.Pool 中的对象会在每次 GC 开始前被整体清空(即“GC 时销毁”),这意味着它不适合做跨请求的缓存,只适合单次处理链路内的临时复用。
容易被忽略的一点:Pool 的 New 函数在 Get 无可用对象时才调用,但它不是并发安全的——多个 goroutine 同时触发 New 可能创建多个实例,只保留一个,其余被立即丢弃。所以 New 函数里别做昂贵初始化(如打开文件、建连接),而应只做轻量构造。