sync.Pool 的 Get() 经常返回 nil 是正常行为因 GC 会清空未引用对象、goroutine 迁移可能导致本地池遗弃New 函数需线程安全且每次返回新实例对象按 P 分片为避免锁竞争但跨 P 调度会触发慢路径Put 前须 Reset 否则复用“脏对象”仅高频、短生命周期、小对象适用大对象或低频使用反而拖慢性能。sync.Pool 的 Get() 为什么经常返回 nil因为 sync.Pool 不保证对象存活——它不是缓存也不是对象池意义上的“稳定容器”。GC 触发时所有未被引用的池中对象会被批量清空而 goroutine 迁移、P 被抢占也可能导致本地池被遗弃。所以 Get() 返回 nil 是正常行为不是 bug。每次 GC 后每个 P 的 victim cache 全清global 链表只保留最近一次 Put() 的对象New 函数只在池为空时兜底调用且可能被多个 goroutine 并发触发必须线程安全、无副作用不要在 New 里复用全局变量如 globalBuf否则多个 goroutine 会并发读写同一实例引发 panic正确做法每次返回全新实例例如 return bytes.Buffer{} 或 return MyStruct{data: make([]byte, 0, 1024)}为什么对象要按 P 分片跨 P 获取代价有多大Go 运行时为每个逻辑处理器P维护独立的本地池目的是避免锁竞争。这是 sync.Pool 高性能的核心设计但也带来调度敏感性。goroutine 在固定 P 上执行时Get()/Put() 走 fast path几乎无开销若 goroutine 被调度到另一个 P比如系统调用返回、抢占再调用 Get() 就会触发 slow path先查本地池再查 shared 队列最后才调 New延迟明显上升高并发下若 goroutine 频繁迁移如大量阻塞 IO本地池利用率下降复用率暴跌甚至不如直接 new压测时观察 runtime.mallocgc 占比和 sync.Pool 相关 allocs profile比看“用了没”更真实Put 前不 Reset 会出什么问题Pool 不做任何状态清理Put() 只是把指针挂进链表底层数组、map、切片长度、字段值全保留。下次 Get() 拿到的就是“脏对象”。bytes.Buffer 复用后不 Reset() → 上次写入内容还在WriteString() 会追加而非覆盖输出错乱结构体里有 map[string]int 字段没清空 → 下次使用时 key 冲突、计数异常甚至 panic切片字段只做 s s[:0]不置 nil → 底层数组持续占用阻碍 GC 回收大内存块推荐统一提供 Reset() 方法例如func (b *Buffer) Reset() { b.data b.data[:0] }哪些场景真能省内存哪些反而拖慢性能sync.Pool 只对「高频分配 短生命周期 小对象」有效。它不是银弹用错比不用更糟。 唱鸭 音乐创作全流程的AI自动作曲工具集 AI 辅助作词、AI 自动作曲、编曲、混音于一体