目录

sync.Pool

sync.Pool

1. 概述

sync.Pool是Go语言标准库中的一个类型,用于管理一组可重用的对象。它的主要目的是减少内存分配和垃圾回收的开销,提高程序的性能。

sync.Pool的工作原理是:当需要创建一个新的对象时,Pool会检查是否有可用的对象。如果有,Pool会返回一个可用的对象,而不是创建一个新的对象。这样可以避免频繁地进行内存分配和垃圾回收。

复用临时对象,减少频繁的内存分配和垃圾回收,提高程序的性能。

2. 基本使用

sync.Pool的使用方法如下:

package main

import (
    "fmt"
    "sync"
)
type MyStruct struct {
    Name string        // 结构体的字段
}

var pool = sync.Pool{
    New: func() interface{} {
        // 创建一个新的对象
        return &MyStruct{}
    },
}

func main() {

    // 首次获取对象
    obj := pool.Get().(*MyStruct)
    fmt.Println("首次对象", obj.Name)

    // 设置对象的字段
    obj.Name = "Hello, World!"
    
    
    // 将对象放回池中
    pool.Put(obj)
    
    // pool中存在对象
    obj2 := pool.Get().(*MyStruct)
    fmt.Println("pool中存在对象", obj2.Name)
    // 再次获取对象
    obj3 := pool.Get().(*MyStruct)
    fmt.Println("再次获取对象", obj3.Name)

}

输出结果:

首次对象 
pool中存在对象 Hello, World!
再次获取对象

3. 使用案例

gin 框架

gin 框架会给每个请求分配一个 Context 用以进行追踪,这就是典型的 sync.pool 使用场景:


func New() *Engine {
	engine := &Engine{
		
	}
	engine.pool.New = func() interface{} {
		return engine.allocateContext()
	}
	return engine
}

func (engine *Engine) allocateContext() *Context {
	return &Context{engine: engine}
}
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	c := engine.pool.Get().(*Context)
	c.writermem.reset(w)
	c.Request = req
	c.reset()

	engine.handleHTTPRequest(c)

	engine.pool.Put(c)
}

从 pool 中获取 Context 对象,用完后又还回去,注意 还回去之前这里也调用了 reset() 方法进行字段清空。

注意事项

  1. 设置 New 方法
  2. 使用时直接 Get
  3. 使用完成后先进行字段清空,然后在 Put 回去。

4. 原理解析

type Pool struct {
	noCopy noCopy

	local     unsafe.Pointer // 每个P对应的本地固定大小池,实际类型是 [P]poolLocal
	localSize uintptr        // 本地数组的大小

	victim     unsafe.Pointer // 前一个生命周期的本地池
	victimSize uintptr        // 前一个生命周期池的大小

	// New 可选地指定一个生成函数
	// 当Get方法无法找到可用对象时,将通过这个函数创建新对象
	// 注意:该字段不可与Get方法的调用并发修改
	New func() any
}

字段详解:

  • noCopy对象,实现了sync.Locker接口,使得内嵌了 noCopy 的对象在进行 go vet 静态检查的时候,可以检查出是否被复制。
  • local 字段存储指向 [P]poolLocal 数组(严格来说,它是一个切片)的指针。localSize 则表示 local 数组的大小。
    • 访问时,根据 P 的 id 去访问对应下标的 local[pid]
    • 通过这样的设计,多个 goroutine 使用同一个 Pool 时,减少了竞争,提升了性能。有点类似于降低锁粒度,分段锁的思想。
  • victimvictimSize 则会在在一轮 GC 到来时,分别“接管” local 和 localSize。
    • victim cache 是一种提高缓存性能的硬件技术;
    • victim 的机制用于减少 GC 后冷启动导致的性能抖动,让分配对象更平滑;
    • sync.Pool 引入的意图在于降低 GC 压力的同时提高命中率。
  • New就是我们指定的新建对象的方法。

Victim Cache 是一种提高缓存性能的硬件技术,主要用于提升缓存命令率。 所谓受害者缓存(Victim Cache),是一个与直接匹配或低相联缓存并用的、容量很小的全相联缓存。当一个数据块被逐出缓存时,并不直接丢弃,而是暂先进入受害者缓存。如果受害者缓存已满,就替换掉其中一项。当进行缓存标签匹配时,在与索引指向标签匹配的同时,并行查看受害者缓存,如果在受害者缓存发现匹配,就将其此数据块与缓存中的不匹配数据块做交换,同时返回给处理器。