作者:躺在地球上的熊 | 来源:互联网 | 2023-09-12 10:18
Flutter、Golang、Python、编译原理、算法、Chrome原理学习系列文章抢先看请关注【码农帮派】:
【Golang学习系列文章,请扫二维码】
上一节中我们通过Buffered channel实现了对象池的功能,但是我们发现在Golang的sync包中有一个Pool,sync.Pool其实是对象缓存机制。
sync.Pool的对象缓存是和Processor关联的。Processor在Golang的MPG中介绍过,M是Machine,一个M关联一个内核对象;P是协程处理器,是M运行的上下文环境;G就是协程。
对于Processor,它的数据对象如下图:
每个Processor包含一个私有对象和一个共享池,很多资料上将私有对象成为私有对象池,这是不合适的,因为它只能缓存一个对象,因此更为准确的应该是私有对象。
其中私有对象是协程安全的,共享池是协程不安全的。私有对象本身已经实现了锁,因此我们在访问私有对象的时候无需自己加锁,而共享池是协程不安全的,因此我们在使用共享池的时候是需要自己加锁的。
sync.Pool对象获取的流程:
-
尝试从私有对象获取
-
私有对象不存在,尝试从当前Processor的共享池中获取
-
如果当前Processor共享池也是空的,那么尝试从其他Processor的共享池中获取
-
如果所有子池都是空的,那么使用sync.Pool的New函数创建一个新的对象,并返回。
sync.Pool对象返回的流程:
-
如果私有对象不存在,则存储到私有对象中
-
如果私有对象已存在,则放入到当前Processor的共享池中
sync.Pool的使用方法:
pool := &sync.Pool {
New: func() interface{} {
return 0
},
}
// 获取对象
item := pool.Get().(int)
// 对象放回
pool.Put(1)
在创建一个sync.Pool的时候,我们指定了一个New函数,保证在私有对象和每个共享池中获取不到对象的时候,可以进行对象初始化创建。
sync.Pool的生命周期
正是因为sync.Pool对象的周期会受到系统GC的影响,因此不能够被当作一种稳定的对象池来使用,只能当作对象缓存。
上面的代码中,我们首次从sync.Pool中获取对象,是通过New函数创建的,当我们使用放回函数放回一个缓存对象之后,再次获取,发现获取到的就是我们放回的对象数据。
我们在程序中手动出发一次GC:
从上面的结果可以看出,即使我们已经向sync.Pool中放回了对象,但是系统GC会将sync.Pool中的对象清空。
需要注意的是,我们首次从sync.Pool中获取对象是通过New函数创建的,我们需要将获取的对象放回到sync.Pool中,否则下次获取的时候,sync.Pool仍然是通过New函数重新创建并返回对象:
上面代码中,只有获取并没有放回,两次获取操作都触发了sync.Pool的New函数重新创建对象。
下面的代码中,我们在多协程中访问sync.Pool的对象,因为我们在协程中只获取,没有返回数据,所有后面协程获取的对象都是通过New函数创建的新对象:
sync.Pool总结:
-
sync.Pool适用于通过复用,来降低复杂对象的创建和GC的代价
-
sync.Pool协程安全的,内部实现了锁机制,会有锁的性能开销
-
sync.Pool中对象的生命周期受到GC的影响,不适合用作链接缓存池,也不适用于需要自己管理对象生命周期的对象池