解析sync.Pool

介绍

特定场景下某对象需要频繁地创建,这种操作可能会影响程序性能,大致原因是2个:
1.创建对象需要申请内存
2.频繁创建对象,会对GC造成较大压力

为解决这个问题sync.Pool诞生了,sync.Pool简单理解就是对象池,其官方说明如下

  • 对象池是一批可被独立存放获取的临时文件的集合
  • 对象池中的对象会在任意时间被移除(无通知)
  • 对象池是并发安全的

通俗的讲对象池其主要做了几个事情

1.复用之前对象,不用每次新建
2.预创建对象,不用临时创建
3.采用性能更高的存储做缓存,提高响应速度

A Pool is a set of temprary objects that may be individually saved and retrieved

Any item stored in the Pool may be removed automatically at any time without notification. If the Pool holds the only reference when this happens,the item might be deallocated

A Pool is safe for use by multiple goroutines simultaneously.

回收策略

在垃圾回收前回收池空间,Pool的目的是在垃圾回收之间重用内存,它不应该避免垃圾回收,而是让垃圾回收变得更有效

收益

1.减少内存分配,降低GC压力 2.缓存频繁使用的对象

缓存周期

对象的最大缓存周期是GC周期,当GC调用时没有被引用的对象都会被清理掉;(Pool包在init会注册一个poolCleanUp函数: 清除所有Pool里的缓存对象,该函数注册之后会在每次gc之前都会调用)

Get方法返回是池中任意一个对象,没有顺序;如果池中没有对象,那么调用New新生成一个;如果没有指定New方法,那么返回nil

适用场景

高频使用且生命周期短的场景,如fmt

相关代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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() interface{}
}
func (p *Pool) Get() interface{} {}
func (p *Pool) Put(x interface{}) {}
func runtime_registerPoolCleanup(cleanup func())
// 禁止调度并返回当前processid
func runtime_procPin() int
func runtime_procUnpin()

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
// demo1
func main(){
p:=&sync.Pool{
New:func()interface{}{
return 0
},
}
a:=p.Get().(int)
p.Put(1)
b:=p.Get().(int)
fmt.Println(a,b)
}

问题

临时对象在什么时间被销毁?

sync包在被初始化的时候,会向 Go 语言运行时系统注册一个函数,这个函数的功能就是清除所有已创建的临时对象池中的值。我们可以把它称为池清理函数。

一旦池清理函数被注册到了 Go 语言运行时系统,后者在每次即将执行垃圾回收时就都会执行池清理。

另外,在sync包中还有一个包级私有的全局变量。这个变量代表了当前的程序中使用的所有临时对象池的汇总,它是元素类型为*sync.Pool的切片。我们可以称之为池汇总列表。

通常,在一个临时对象池的Put方法或Get方法第一次被调用的时候,这个池就会被添加到池汇总列表中。正因为如此,池清理函数总是能访问到所有正在被真正使用的临时对象池。

更具体地说,池清理函数会遍历池汇总列表。对于其中的每一个临时对象池,它都会先将池中所有的私有临时对象和共享临时对象列表都置为nil,然后再把这个池中的所有本地池列表都销毁掉。

最后,池清理函数会把池汇总列表重置为空的切片。如此一来,这些池中存储的临时对象就全部被清除干净了。

如果临时对象池以外的代码再无对它们的引用,那么在稍后的垃圾回收过程中,这些临时对象就会被当作垃圾销毁掉,它们占用的内存空间也会被回收以备他用。

Get流程

参考

Go 1.13中 sync.Pool 是如何优化的?
golang的对象池sync.pool源码解读