sync.pool不是自动复用魔法开关,它只缓存指针、不管理状态、不保证对象干净;get后必须手动reset,否则残留数据导致错乱;put前需确保对象无任何goroutine引用,new仅兜底创建全新对象。

sync.Pool 不是“自动复用对象”的魔法开关,它只缓存指针、不管理状态、不保证对象干净——用错比不用更慢。
Get 后不调 Reset 就等于把脏数据塞回池子
sync.Pool 的 Get() 返回的可能是别人上次 Put() 进去的“二手”对象。它的 len、cap、字段值全不可控。常见错误现象包括:bytes.Buffer.String() 返回拼接错乱内容、json.Unmarshal 解析出残留 key、自定义结构体的 Err 字段仍是上一次的非 nil 值。
必须手动重置,不能依赖 New 函数:
-
bytes.Buffer:立刻调buf.Reset() - 固定 cap 的
[]byte:用slice = slice[:0]清空长度,保留底层数组 - 自定义结构体:封装幂等
Reset()方法,覆盖所有可变字段(data = data[:0]、err = nil、clear(cache))
Put 的时机和前提比 defer 更关键
在 HTTP handler 里写 defer pool.Put(obj) 看似整洁,但一旦 panic 或提前 return,defer 不执行,对象永久丢失;更危险的是,若把 obj 传给了异步 goroutine,主 goroutine 却已 Put,就会触发跨 goroutine 使用 + 重复 Put,导致 panic 或数据竞争。
稳妥做法是:
- 在明确的、不会跳过的出口处
Put,比如响应写完后、请求处理彻底结束前 -
Put前必须确保该对象不再被任何 goroutine 引用(包括子 goroutine) - 不要把含
sync.Mutex、未关闭io.ReadCloser、外部指针引用的对象丢进去——Put不会帮你Unlock或Close
New 函数不是构造器,而是兜底工厂
New 只在 Get() 发现池空时才调,且可能被多个 goroutine 并发调多次。它不控制对象来源,也不保证每次 Get 都触发它。
典型错误写法:
-
New: func() interface{} { return &globalBuf }—— 多个 goroutine 共享同一实例,必然竞态 -
New: func() interface{} { log.Println("created"); return new(bytes.Buffer) }—— 日志副作用,调用时机不可控
正确写法需满足两个条件:
- 每次返回全新对象(如
&MyStruct{Data: make([]byte, 0, 512)}) - 返回对象字段全为零值,或内部已显式初始化(如
map字段赋空 map,而非nil)
对象是否适合进 Pool,核心看三点
别被“高频创建”带偏节奏。真正能放进 sync.Pool 的对象,必须同时满足:
- 短生命周期:只在单次请求/单次解析中使用,不跨 goroutine 长期持有
- 大小稳定:如
make([]byte, 0, 1024),避免底层数组反复扩容导致池内碎片堆积 - 可安全 Reset:所有字段都能无副作用清零(指针置
nil、切片截断、map清空),不含锁、finalizer、未关闭资源
禁用对象举例:*http.Request(含不可复用的 Body 和 Context)、*gin.Context(带注册的中间件状态)、含 sync.RWMutex 的结构体、*sql.DB(连接池自己管)。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











