跳到主要内容
为何最终我放弃了 Go 的 sync.Pool | 极客日志
Go / Golang
为何最终我放弃了 Go 的 sync.Pool 综述由AI生成 sync.Pool 是 Go 语言提供的对象重用机制,旨在保存临时取还对象以减少内存分配和 GC 压力。它适用于创建成本高、生命周期短、使用频率高且可安全重置的场景。然而,对于存储驱动等无状态、创建成本低或需要连接池管理的资源,sync.Pool 并不适合。文章通过 Gin 框架 Context 复用案例及底层结构剖析,结合性能测试数据,阐述了 sync.Pool 的适用边界与潜在误区,帮助开发者更准确地选型。
FlinkHero 发布于 2026/3/16 更新于 2026/5/21 29 浏览为何最终我放弃了 Go 的 sync.Pool
一、使用场景
一句话总结:保存和复用临时对象,减少内存分配,降低 GC 压力。
1.1、引入
举个简单的例子:
type User struct {
ID int64 `json:"id"`
Username string `json:"username"`
Email string `json:"email"`
Profile [512 ]byte `json:"profile_data"`
}
var buf, _ = json.Marshal(User{
ID: 1 ,
Username: "john_doe" ,
Email: "[email protected] " ,
})
user := &User{}
json.Unmarshal(buf, user)
JSON 的反序列化在数据解析和网络通信中非常常见。当程序并发度非常高的情况下,短时间内需要创建大量临时对象。而这些临时对象都是分配在堆上的,会给 GC 造成很大的压力,严重影响程序的性能。所以可以通过 sync.Pool 来解决。
1.2、什么是 sync.Pool?
Go 语言从 1.3 版本开始提供对象重用机制,即 sync.Pool。sync.Pool 是 sync 包下的一个组件,可以作为保存临时取还对象的一个池子。同时 sync.Pool 是可伸缩且并发安全的,它的大小受限于内存的大小。sync.Pool 用于存储那些被分配了但是没有被使用,而未来还会使用的值。
这样就不用再次经过内存分配,而是直接复用对象,减轻 GC 压力,从而提升性能。
但个人觉得它的命名可能造成误解,因为 Pool 里装的对象可以被无通知地被回收,可能 sync.Cache(临时缓存) 是一个更合适的名字。
二、如何使用
sync.Pool 的使用方式非常简单:
2.1、声明对象池
只需要实现 New 函数即可,当对象池 (sync.Pool) 中没有对象时,就会自动调用 New 函数进行初始化。
var userPool = sync.Pool{
New: func () interface {} {
return new (User)
},
}
2.2、GET & PUT
user := userPool.Get().(*User)
json.Unmarshal(buf, user)
userPool.Put(user)
Get() 用于从对象池中获取对象,因为返回值是 interface{},因此需要类型转换。
Put() 则是在对象使用完毕后,返回对象池。
三、实例
3.1、标准库中的应用 Go 语言标准库大量使用了 sync.Pool,例如 fmt 和 encoding/json。以下是 fmt.Printf 的源代码片段:
type pp struct {
buf buffer
...
}
var ppFree = sync.Pool{
New: func () interface {} {
return new (pp)
},
}
func newPrinter () *pp {
p := ppFree.Get().(*pp)
p.panicking = false
p.erroring = false
p.wrapErrs = false
p.fmt.init(&p.buf)
return p
}
func (p *pp) free() {
if cap (p.buf) > 64 <<10 {
return
}
p.buf = p.buf[:0 ]
p.arg = nil
p.value = reflect.Value{}
p.wrappedErr = nil
tpFree.Put(p)
}
func Fprintf (w io.Writer, format string , a ...interface {}) (n int , err error ) {
p := newPrinter()
p.doPrintf(format, a)
n, err = w.Write(p.buf)
p.free()
return
}
func Printf (format string , a ...interface {}) (n int , err error ) {
return Fprintf(os.Stdout, format, a...)
}
fmt.Printf 的调用是非常频繁的,利用 sync.Pool 复用 pp 对象能够极大地提升性能,减少内存占用,同时降低 GC 压力。
3.2、Gin 框架的应用 (context) 在 Gin 框架中,Context 对象代表了处理一个 HTTP 请求的上下文。每个请求都需要一个 Context,请求处理完毕,Context 的生命周期也就结束了。
高频的创建于销毁 :在高并发下,每秒会创建和销毁大量 Context 对象。
固定生命周期 :Context 的生命周期始于请求到来,止于请求处理完毕,非常短暂。
3.2.1、定义对象池 在 gin.Engine 结构体的定义中,pool 字段就是一个 sync.Pool。
type Engine struct {
pool sync.Pool
}
3.2.2、初始化对象池 在创建 Gin 引擎实例的时候,会初始化 sync.Pool,并指定 New 函数。当池子中无对象可用的时候,会调用此函数创建新的 Context。
func New () *Engine {
engine.pool.New = func () any {
return engine.allocateContext(engine.maxParams)
}
return engine
}
func (engine *Engine) allocateContext(maxParams uint16 ) *Context {
v := make (Params, 0 , maxParams)
return &Context{engine: engine, params: &v, skippedNodes: &skippedNodes}
}
3.2.3、从池中获取 Context 当 HTTP 请求到达时,Gin 会从 sync.Pool 中获取一个 Context 对象。
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
c := engine.pool.Get().(*Context)
c.writermem.reset(w)
c.Request = req
c.reset()
handleHTTPRequest(c)
engine.pool.Put(c)
}
3.2.4、处理请求后放回池中 请求处理完毕后,Gin 会将 Context 重置并放回 sync.Pool 中,以供后面复用。
切记,重点是要重置的,如调用 c.reset()。确保放回的是干净的上下文。
四、我在项目中的实战
4.1、为何最初选择 sync.Pool 我的目的是设计了一个支持多存储驱动的图片上传模块,重点解决了并发性能、资源管理和动态切换的问题。
为了解决所谓的高并发,复用实例的问题,我就自然的想到去使用 sync.Pool,但问题来了!
对象复用 :避免频繁创建和销毁对象
并发安全 :多个用户可同时使用不同驱动
type MultiDriverPool struct {
pools map [string ]ObjectPool
mu sync.RWMutex
current string
}
type ObjectPool interface {
Get() (Driver, error )
Put(Driver) error
Close()
Size() int
Available() int
}
4.2、又为何选择放弃 sync.Pool
4.2.1、存储驱动通常是无状态的 比如七牛云驱动使用相同的 AccessKey 和 SecretKey,每个实例都执行相同的操作,没有必要维护多个实例。实际上,一个驱动实例就可以处理所有请求,而且通常驱动本身是线程安全的(或者可以通过在方法内部分配资源来做到线程安全)。
换句话说就是:认为每个驱动实例需要频繁创建和销毁,但实际上驱动实例是可以复用的,而且创建成本不高,并且'存储驱动是无状态的'!
五、总结
创建成本高 对象初始化有显著开销
生命周期短 使用后很快就不再需要
使用频率高 大量并发创建销毁
可安全重置 能完全清理之前的状态
var driverPool = sync.Pool{
New: func () interface {} {
return &QiniuDriver{}
},
}
var dbPool = sync.Pool{
New: func () interface {} {
return sql.Open(...)
},
}
var configPool = sync.Pool{
New: func () interface {} {
return loadConfig()
},
}
sync.Pool 的核心作用,不是资源管理。而是通过保存和复用临时对象,减少内存分配,降低 GC 压力!
六、sync.Pool 的底层剖析
6.1 底层结构体 type Pool struct {
noCopy noCopy
local unsafe.Pointer
localSize uintptr
victim unsafe.Pointer
victimSize uintptr
New func () any
}
6.2 重点 在 Pool 的底层,核心有两点:分别是 local 与 victim。
6.2.1 local unsafe.Pointer local 是一个按 P(GOMAXPROCS)分片的本地对象池。每个 P 都有自己的 poolLocal,无需锁,极快。Get / Put 操作优先访问本地池,不需要加锁。
6.2.2 victim Go 认为 Pool 内的对象是可丢弃的,所以每次 GC 会清空 pool.local。为了避免冲击(比如刚清空就马上又需要大量对象),Go 引入了:上一 GC 周期的 pool.local 备份。避免 GC 后对象全部被清空导致性能抖动。
七、性能测试
7.1 测试主函数 type User struct {
ID int64 `json:"id"`
Username string `json:"username"`
Email string `json:"email"`
Profile [512 ]byte `json:"profile_data"`
}
var userPool = sync.Pool{
New: func () interface {} {
atomic.AddUint64(&poolMisses, 1 )
return new (User)
},
}
var bufPool = sync.Pool{
New: func () interface {} {
return new (bytes.Buffer)
},
}
var totalGets uint64
var poolMisses uint64
func getUser () *User {
atomic.AddUint64(&totalGets, 1 )
return userPool.Get().(*User)
}
func putUser (u *User) {
u.ID = 0
u.Username = ""
u.Email = ""
for i := range u.Profile {
u.Profile[i] = 0
}
userPool.Put(u)
}
func processUser (data []byte ) *User {
u := getUser()
_ = json.Unmarshal(data, u)
return u
}
func handleProcess (w http.ResponseWriter, r *http.Request) {
var b bytes.Buffer
_, _ = io.Copy(&b, r.Body)
u := processUser(b.Bytes())
defer putUser(u)
}
func handleMetrics (w http.ResponseWriter, _ *http.Request) {
hits := atomic.LoadUint64(&totalGets) - atomic.LoadUint64(&poolMisses)
_, _ = w.Write([]byte ("sync_pool_gets " + strconv.FormatUint(atomic.LoadUint64(&totalGets), 10 )+"\n" ))
_, _ = w.Write([]byte ("sync_pool_misses " + strconv.FormatUint(atomic.LoadUint64(&poolMisses), 10 )+"\n" ))
_, _ = w.Write([]byte ("sync_pool_hits " + strconv.FormatUint(hits, 10 )+"\n" ))
}
func main () {
http.HandleFunc("/process" , handleProcess)
http.HandleFunc("/metrics" , handleMetrics)
_ = http.ListenAndServe(":8080" , nil )
}
7.2 对象的复用率 func TestHTTPConcurrent (t *testing.T) {
data := []byte (`{"id":10,"username":"concurrent","email":"[email protected] "}` )
req := httptest.NewRequest(http.MethodPost, "/process" , bytes.NewBuffer(data))
n := 500
var wg sync.WaitGroup
wg.Add(n)
for i := 0 ; i < n; i++ {
go func () {
defer wg.Done()
w := httptest.NewRecorder()
handleProcess(w, req)
if w.Code != http.StatusOK {
t.Errorf("bad status" )
}
}()
}
wg.Wait()
t.Logf("totalGets=%d, poolMisses=%d" , totalGets, poolMisses)
}
=== RUN TestHTTPConcurrent
pool_test.go:65: totalGets=500 (调用 get 的总次数),poolMisses=4 (新 new 的次数)
— PASS: TestHTTPConcurrent (0.00s)
PASS
但若大家自己测,由于处于不同环境,结果应该会有些许波动。
7.3 对象复用性能测试 采用基准测试:用来测性能的测试,包括耗时、内存分配、GC 压力等。
b.N 是测试循环次数(Go 自动调整)。b.ReportAllocs():显示内存分配次数。b.ResetTimer():重置计时器(忽略前面初始化的耗时)。
func BenchmarkWithoutPool (b *testing.B) {
data := []byte (`{"id":123,"username":"user123","email":"[email protected] "}` )
b.ReportAllocs()
b.ResetTimer()
for i := 0 ; i < b.N; i++ {
u := &User{}
_ = json.Unmarshal(data, u)
}
}
func BenchmarkWithPool (b *testing.B) {
data := []byte (`{"id":123,"username":"user123","email":"[email protected] "}` )
b.ReportAllocs()
b.ResetTimer()
for i := 0 ; i < b.N; i++ {
u := getUser()
_ = json.Unmarshal(data, u)
putUser(u)
}
}
测试项 ns/op(每次操作耗时) B/op(分配内存字节数) allocs/op(分配次数) WithoutPool 721 ns 816 B 7 allocs WithPool 664 ns 240 B 6 allocs
可以从 B/op(分配内存字节数),近 4 倍的差距,看出性能的差距。当然大家自测时,应该会出现偏差,要以 withoutPool 与 withPool 的差距作为对比标准。
八、自测
sync.Pool 的主要作用是什么?为什么它能减少 GC 压力?
sync.Pool.New 是在什么情况下被调用的?
为什么从 pool 取出的对象必须重置(reset)?
为什么在你的代码中,putUser() 必须把结构体所有字段清空?
为什么结构体字段清空了却依旧要 Reset()?
sync.Pool 为什么不是普通的缓存?它有什么生命周期特性?
相关免费在线工具 Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown转HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
HTML转Markdown 将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
JSON 压缩 通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
JSON美化和格式化 将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online