sync包01:sync.Pool

3 阅读3分钟

什么是sync.Pool?

sync.Pool​ 是 Go 语言标准库中提供的一个并发安全临时对象池。它的核心作用是为那些创建成本较高、需要频繁创建和销毁的对象提供缓存复用机制
因此它具有以下的特点:

  1. 存储可以重用的对象
  2. 在多个goroutine中可以安全并发地访问
  3. 垃圾回收时池中的对象可能会被清理
  4. 池的大小由运行时动态管理

其结构如下:

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() {  // 正确的指针接收者
    // 不会复制
}

locallocalSize

  • local: 指向每个P(goroutine调度器中的处理器)的本地缓存池

  • localSize: 本地缓存池数组的大小,等于运行时P的数量

  • 实现无锁获取,每个P操作自己的本地缓存,避免竞争

victimvictimSize

  • victim: 指向上一轮GC周期存活的对象缓存

  • victimSize: 受害者缓存的大小

  • 实现两代回收机制:当前local + 上一代victim

  • 减少GC引起的性能抖动,对象可能多存活一个GC周期

这是 sync.Pool防止 GC 引起性能波动的关键设计(两代回收机制)

  1. 问题:如果没有 victim,每次 GC 时,local中所有未被使用的对象都会被清空。如果 GC 后立即有大量请求,会导致 New函数被频繁调用,产生瞬时压力。

  2. 解决:引入 victim后,GC 的执行流程变为:

    • GC 开始时:将当前的 local缓存“降级”为 victim,同时将旧的 victim清空。
    • GC 结束后local现在是空的。
  3. 效果:当 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)
}