Golang对象池实现

一.sync.Pool定义

       我们通常用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偷走。随意临时对象池适合存储一些临时数据,不适合用来存储数据库连接等持久化存储的对象。

 

 

 

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值