sync.Pool是一个协程安全的临时对象存储池。主要是用来缓存频繁使用的对象,用以减少 GC 压力。
使用
先来看看官方提供的示例,利用sync.Pool模拟实现的一个日志打印函数。
var bufPool = sync.Pool{ |
核心模式就是:
- 实例化
Pool对象,注入New方法。
通过var创建一个Pool对象,并向其注入了一个New方法,用于创建缓存对向。 Get取用对象,并恢复清洗使用痕迹。
使用Get方法从池中取得一个对象,然后断言为一个指向bytes.Buffer的指针,并通过Reset重置bytes.Buffer内容。- 使用对象拼接并打印日志
- 通过
Put暂存使用完的缓存对象 - 重复 2~4 步骤
注意事项
- Pool 不适用于需要长期保持的对象,因为 Pool 中的对象不保证存活,可能被 GC 回收。如数据库连接。
- Pool 不适合保存数据。无法预测 Pool 中的 GC 行为。
- Pool 不适合保存创建无法预测的对象。
源码解析
数据结构解析

Pool
type Pool struct { |
nocopy
sync 包中用来避免对象被拷贝的一种方法。正常编译不受影响,但使用go vet时候会进行提示。
我们可以使用如下代码进行测试:
var bufPool = sync.Pool{ |
go vet . |
local && localSize
一个固定长度的本地对象池数组,准确的类型为[P]poolLocal,其中 P 对应runtime.GOMAXPROCS(0)返回值,如果在 GC 间进行了修改,旧 local 会被丢弃,并且进行重新分配新的 local。localSize 是用来保存 local 长度的,用以对local数组空间进行遍历。
victim && victimSize
victim 和 victimSize,其实是旧的 local 和 localSize, 在执行 GC 时会将原 victim 和 victimSize 回收,并将 local 和 localSize 放入。这样操作是可以避免高负载时,突然 GC 造成的抖动(需要从新创建大量对象以满足负载)。
New
使用用来创建对象的函数,由用户自定义。
poolLocal
|
- poolLocalInternal 是数据的存储空间,其中 private 是当前 P 私有的空间,可以存取一个对象。
- shared 是一个双向链表, 头结点只有当前 P 能访问(只有当前 P 能放入对象)所有不用做并发控制。尾结点所有 P 都能访问,所以要做并发控制。shared 中结点 poolChainElt 保存的元素是一个数组实现的循环链表 poolDequeue。
- poolDequeue 中 headTail 为一个 uint64 数据,高 32 低 32 为分别保存循环链表的头尾。数据元素为 eface。另外这个循环队列如果满了以后,会创建一个两倍长度的新队列。
- eface 中是两指针,分别保存存储对象的类型和值。
关键流程
Get
func (p *Pool) Get() any { |
Get 流程
- 调用 pin 获取 poolLocal,尝试从 private 中获取对象,如果由可用对象直接返回
- 否则尝试从 shared 中获取,如果有则返回
- 否则执行 getSlow 流程
- 依旧没有获取到对象则创建新的对象
|
getSlow 流程
- 尝试从其他 P 的 poolLocal.shared 中获取对象,成功则返回
- 否则尝试从 victim 中获取对象,顺序依旧是 private、local shared,shared,成功则返回
- 否则标记 victim 空后返回空,进入创建新对象的流程
Put
// Put adds x to the pool. |
Put 的流程就简单很多了
- 先尝试存在本地 private
- 然后直接存在 local shared 里。
// 这里看一看的 循环队列双倍增长的逻辑 |
GC
- 本文作者: Tiny Beer
- 本文链接: https://tinybeer.github.io/2024/05/29/sync-Pool详解/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!
