sync pool使用来存放临时变量的一个缓冲区,但是这个缓冲区并不可靠,每次gc的时候,都会首先清除缓冲区,所以,假如一个slice仅仅存放在 Pool 中,而没有其他地方引用,则会被当成垃圾清理掉。
概念
A Pool is a set of temporary 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’s purpose is to cache allocated but unused items for later reuse, relieving pressure on the garbage collector. That is, it makes it easy to build efficient, thread-safe free lists. However, it is not suitable for all free lists.
An appropriate use of a Pool is to manage a group of temporary items silently shared among and potentially reused by concurrent independent clients of a package. Pool provides a way to amortize allocation overhead across many clients.
An example of good use of a Pool is in the fmt package, which maintains a dynamically-sized store of temporary output buffers. The store scales under load (when many goroutines are actively printing) and shrinks when quiescent.
图示
sync.pool的结构组成如上图所示,在这里可能会有两个问题
- 我们实例化 Sync.Pool的时候,为什么实例化了一个LocalPool数组,怎么确定我的数据应该存储在LocalPool数组的哪个单元?
- PoolLocalInternal 里面的成员有private和shared,为什么要做这两种区分?
源码分析
Put
Put()
1 | func (p *Pool) Put(x interface{}) { |
pin()
1 | func (p *Pool) pin() *poolLocal { |
runtime_procPin()
这个是获取当前运行的pid,具体实现没有查到, 但是runtime_procPin()
返回的数值范围是由 runtime.GOMAXPROCS(0)
决定的。网上有篇文章可以参考一下《Golang 的 协程调度机制 与 GOMAXPROCS 性能调优》,这里暂不深入
PinSlow()
1 | func (p *Pool) pinSlow() *poolLocal { |
Put 逻辑
综上,Put的基本操作逻辑就是
- 获取当前执行的
Pid
- 根据Pid,找到对应的
PoolLocal
,接着使用里面PoolLocalInternal
- 优先存入
PoolLocalInternal
的private
属性,其次粗如PoolLocalInternal
的shared
这个slice里面
Get
Get()
1 | func (p *Pool) Get() interface{} { |
getSlow()
1 | func (p *Pool) getSlow() (x interface{}) { |
通过上面的逻辑可以看出,shared
里面的数据是会被其他的P检索到的,而 private
里面的数据是不会的,所以在获取shared
里面数据的时候,需要加锁
poolCleanup
这个函数是Pool包里面提供的,用来清理Pool的,但是官方的实现略显粗暴
1 | func poolCleanup() { |
这个函数会在GC之前调用,这也就解释了官方的下面一句话
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.
如果一个数据仅仅在Pool中有引用,那么就需要担心这个数据被GC清理掉
问题分析
针对于上面提出的两个问题,做一下简单的分析
我们实例化 Sync.Pool的时候,为什么实例化了一个LocalPool数组,怎么确定我的数据应该存储在LocalPool数组的哪个单元?
这里的LocalPool是根据不同的pid来区分的,保证private数据的线程安全,程序运行的时候可以获取到pid,然后使用pid作为LocalPool的索引,找到对应的地址即可
PoolLocalInternal 里面的成员有private和shared,为什么要做这两种区分?
private
是 P 专属的, shared
是可以被其他的P获取到的
参考文档
《sync.Pool源码实现》
《Go 语言学习笔记 - 雨痕》