什么是sync.Pool?
sync.Pool 是 Go 语言标准库中提供的一个并发安全的临时对象池。它的核心作用是为那些创建成本较高、需要频繁创建和销毁的对象提供缓存和复用机制。
因此它具有以下的特点:
- 存储可以重用的对象
- 在多个goroutine中可以安全并发地访问
- 垃圾回收时池中的对象可能会被清理
- 池的大小由运行时动态管理
其结构如下:
type Pool struct {
noCopy noCopy
local unsafe.Pointer // local fixed-size per-P pool, actual type is [P]poolLocal
localSize uintptr // size of the local array
victim unsafe.Pointer // local from previous cycle
victimSize uintptr // size of victims array
// New optionally specifies a function to generate
// a value when Get would otherwise return nil.
// It may not be changed concurrently with calls to Get.
New func() any
}
noCopy类型是一个空结构,不占额外内存,是 Go 语言标准库内部使用的一种防止值复制的标记结构。下面是一个示例:
type MyStruct struct {
noCopy noCopy
data []byte
}
func (m MyStruct) BadMethod() { // 这里会导致编译警告
// 值接收者会导致复制
}
func (m *MyStruct) GoodMethod() { // 正确的指针接收者
// 不会复制
}
local 和 localSize
-
local: 指向每个P(goroutine调度器中的处理器)的本地缓存池
-
localSize: 本地缓存池数组的大小,等于运行时P的数量
-
实现无锁获取,每个P操作自己的本地缓存,避免竞争
victim 和 victimSize
-
victim: 指向上一轮GC周期存活的对象缓存
-
victimSize: 受害者缓存的大小
-
实现两代回收机制:当前local + 上一代victim
-
减少GC引起的性能抖动,对象可能多存活一个GC周期
这是
sync.Pool防止 GC 引起性能波动的关键设计(两代回收机制) 。
-
问题:如果没有
victim,每次 GC 时,local中所有未被使用的对象都会被清空。如果 GC 后立即有大量请求,会导致New函数被频繁调用,产生瞬时压力。 -
解决:引入
victim后,GC 的执行流程变为:- GC 开始时:将当前的
local缓存“降级”为victim,同时将旧的victim清空。 - GC 结束后:
local现在是空的。
- GC 开始时:将当前的
-
效果:当
Get时,首先在local中找,找不到则去victim中“抢救”一次。这样,一个对象从被Put到被彻底 GC 掉,至少会经历两次 GC 周期,显著提升了对象的存活时间和复用几率,平滑了 GC 后的性能曲线。
New
-
可选的回调函数
-
当缓存池为空时,
Get()方法会调用此函数创建新对象 -
如果未设置
New且池为空,Get()返回nil
sync.Pool的使用
先定义要复用对象的结构体
type ResponseBuilder struct {
statusCode int
headers map[string]string
body []byte
createdAt time.Time
}
对象对应的Reset()方法
func (rb *ResponseBuilder) Reset() {
rb.statusCode = http.StatusOK
rb.body = rb.body[:0] // 清空内容,但保持底层数组
// 清空headers
for k := range rb.headers {
delete(rb.headers, k)
}
// 设置默认header
rb.headers["Content-Type"] = "application/json"
rb.headers["X-Pooled"] = "true"
}
创建对象池
var responsePool = &sync.Pool{
New: func() interface{} {
// 当池为空时,创建新的 ResponseBuilder
return &ResponseBuilder{
headers: make(map[string]string, 5),
body: make([]byte, 0, 1024), // 初始容量 1KB
createdAt: time.Now(),
}
},
}
封装一个从对象池获取对象的方法
func AcquireResponseBuilder() *ResponseBuilder {
builder := responsePool.Get().(*ResponseBuilder)
// 重置对象状态
builder.Reset()
return builder
}
封装将对象放回池中的方法
func ReleaseResponseBuilder(builder *ResponseBuilder) {
// 清理大内存,避免池中对象占用过多内存
if cap(builder.body) > 1024 * 1024 { // 如果超过1MB
builder.body = nil // 让GC回收大内存
}
responsePool.Put(builder)
}