我们通常用golang来构建高并发场景下的应用,但是由于golang内建的GC机制会影响应用的性能,为了减少GC,golang提供了对象内存空间重用的机制,也就是sync.Pool对象池。sync.Pool是可伸缩的,并发安全的。其大小仅受限于内存的大小,可以被看作是一个存放可重用对象的值的容器。
设计的目的是存放已经分配的但是暂时不用的对象,在需要用到的时候直接从pool中取。
sync.Pool同我们常用的pool概念不同。我们常用的比如连接池,池里存放的是空闲的链接,当需要时从池中取出。而sync.Pool存放的并不是对象,当需要这个对象时再从sync.Pool中取出。sync.Pool存放的是对象的内存空间。应用sync.Pool,每次不用重新分配新的空间,而是服用sync.Pool中存放的已有空间。
结构
1 | type Pool struct { |
- noCopy 该字段用来约束在第一次使用后不能被复制。参见
- local 每个P都会在本地上分配这样一个poolLocal,索引值就是p的id。local是一个poolLocalInternal的切片指针。private存放的对象只能由创建的P读写,shared则会在多个P之间共享。
1 | type poolLocalInternal struct { |
- localSice
- victim 会将pool内数据拷贝一份,避免GC将其清空,即使没有引用的内容也可以保留最多两轮GC。
- victimSize
- New 创建时候指定New方法用于创建默认对象。当Get()时池内没有元素,会调用New方法新分配一个空间并返回。
初始化操作
1 | func init() { |
pool.go文件中通过init()方法初始化注册了一个函数poolCleanup(),这个函数会在每次GC前STW的时候唤醒。
1、现将oldPools中的victim置为空。
2、将当前local中的内容存放到victim中。
3、oldPools指向allPools,allPools置空。
victim为golang1.13新增的字段,通过此方式保证池中的对象保留最多两轮GC。
Put
1 | func (p *Pool) Put(x interface{}) { |
- Put()方法会在放如池前,丢弃25%的对象,这部分对象的空间不会被复用。
- p.pin()获取本地的池。
- pool优先把元素放在private池中,如果private不为空,则放在shared池中。
Get
1 | func (p *Pool) Get() interface{} { |
- p.pin()获取本地的池和当前P的id。
- 首先从本地的private中读取,若取不到则从本地的shared中取。
- 若依然取不到,则会通过getSlow()方法从其他的P中获取。
- 如果其他的P中依然没有则会调用New()方法新分配一个空间返回。
1 | func (p *Pool) pin() (*poolLocal, int) { |
pin()方法会首先将当前G同当前的P绑定,禁用抢占,并且返回P的本地池和P的id。
1 | func (p *Pool) getSlow(pid int) interface{} { |
getSlow()方法会先从其他的P的shared中偷取空间。
如果没有偷到,则会在本地的victim中获取。然后再次从其他的P中偷取空间。
从源码中可以看出,当pool从本地的shared中取对象时从队列的头部取出,从其他P偷取对象的时候是从尾部取出。
流程图如上