我们通常用golang来构建高并发场景下的应用,但是由于golang内建的GC机制会影响应用的性能,为了减少GC,golang提供了对象重用的机制,也就是sync.Pool对象池。 sync.Pool是可伸缩的,并发安全的。其大小仅受限于内存的大小,可以被看作是一个存放可重用对象的值的容器。 设计的目的是存放已经分配的但是暂时不用的对象,在需要用到的时候直接从pool中取。
官方的解释:临时对象池是一些可以分别存储和取出的临时对象,池中的对象会在没有任何通知的情况下移出(释放或重新使用)。pool在多协程的环境下是安全的,在fmt包中有一个使用pool的例子,它维护了一个动态大小的输出buffer。另外,一些短生命周期的对象不适合使用pool来维护,这种情况使用go自己的free list更高效。
二.sync.Pool的实现2.1使对象池高效
为了使多个goroutine操作同一个pool做到高效,sync.pool为每一个p都分配了一个子池。当执行get或者put操作时,会对当前goroutine挂载的子池操作。每个子池都有一个私有对象和共享列表对象,私有对象只有对应的p能够访问,因为同一个p同一时间只能操作执行一个goroutine,因此对私有对象的操作不需要加锁;但共享列表是和其他P分享的,因此操作是需要加锁的。
获取对象的过程:
- 固定某个P,尝试从私有对象中获取, 如果是私有对象则返回该对象,并把私有对象赋空。
- 如果私有对象是空的,需要加锁,从当前固定的p的共享池中获取-并从该共享队列中删除这个对象。
- 如果当前的子池都是空的,尝试去其他P的子池的共享列表偷取一个,如果用户没有注册New函数则返回nil。
归还对象的过程:
- 固定到某个p,如果私有对象为空则放到私有对象。
- 如果私有对象不为空,加锁,加入到该P子池的共享列表中。
2.2 对象池的详细实现
2.2.1 对象池结构
type Pool struct {
noCopy noCopy //防止copy
local unsafe.Pointer //本地p缓存池指针
localSize uintptr //本地p缓存池大小
//当池中没有对象时,会调用New函数调用一个对象
New func() interface{}
}
2.2.2 获取对象池中的对象
func (p *Pool) Get() interface{} {
if race.Enabled {
race.Disable()
}
//获取本地的poolLocal对象
l := p.pin()
//先获取private池中的私有变量
x := l.private
l.private = nil
runtime_procUnpin()
if x == nil {
//查找本地的共享池,因为本地的共享池可能被其他p访问,所以要加锁
l.Lock()
last := len(l.shared) - 1
if last >= 0 {
//如果本地共享池有对象,取走最后一个
x = l.shared[last]
l.shared = l.shared[:last]
}
l.Unlock()
//查找其他p的共享池
if x == nil {
x = p.getSlow()
}
}
if race.Enabled {
race.Enable()
if x != nil {
race.Acquire(poolRaceAddr(x))
}
}
//未找到其他可用元素,则调用New生成
if x == nil && p.New != nil {
x = p.New()
}
return x
}
从共享池中获取可用元素:
func (p *Pool) getSlow() (x interface{}) {
// See the comment in pin regarding ordering of the loads.
size := atomic.LoadUintptr(&p.localSize) // load-acquire
local := p.local // load-consume
// Try to steal one element from other procs.
pid := runtime_procPin()
runtime_procUnpin()
for i := 0; i < int(size); i++ {
l := indexLocal(local, (pid+i+1)%int(size))
l.Lock()
last := len(l.shared) - 1
if last >= 0 {
x = l.shared[last]
l.shared = l.shared[:last]
l.Unlock()
break
}
l.Unlock()
}
return x
}
2.2.3 归还对象池中的对象
func (p *Pool) Put(x interface{}) {
if x == nil {
return
}
if race.Enabled {
if fastrand()%4 == 0 {
//1/4的概率会把该元素扔掉
return
}
race.ReleaseMerge(poolRaceAddr(x))
race.Disable()
}
l := p.pin()
if l.private == nil {
//赋值给私有变量
l.private = x
x = nil
}
runtime_procUnpin()
if x != nil {
//访问共享池加锁
l.Lock()
l.shared = append(l.shared, x)
l.Unlock()
}
if race.Enabled {
race.Enable()
}
}
三.sync.Pool 使用
// 一个[]byte的对象池,每个对象为一个[]byte
var bytePool = sync.Pool{
New: func() interface{} {
b := make([]byte, 512)
return &b
},
}
func main() {
a := time.Now().Unix()
// 不使用对象池
for i := 0; i < 1000000000; i++{
obj := make([]byte,512)
_ = obj
}
b := time.Now().Unix()
// 使用对象池
for i := 0; i < 1000000000; i++{
obj := bytePool.Get().(*[]byte)
_ = obj
bytePool.Put(obj)
}
c := time.Now().Unix()
fmt.Println("without pool ", b - a, "s")
fmt.Println("with pool ", c - b, "s")
}
// without pool 17 s
// with pool 12 s
四.sync.Pool的使用场景
sync.Pool的get方法不会对获取到的对象做任何的保证,因为放入的本地子池中的值可能在任何是由被删除,而且不会通知调用者。放入共享池的值有可能被其他的goroutine偷走。随意临时对象池适合存储一些临时数据,不适合用来存储数据库连接等持久化存储的对象。