golang临时对象池

419 阅读3分钟

一、什么时临时对象池

golang提供了对象重用机制,也就是sync.Pool

pool用来缓存已经申请但未被使用 接下来可能会被使用的内存,以此缓解GC的压力

sync.Pool是并发安全的,可伸缩的,大小仅受限于内存的大小

sync.Pool非常适用于在并发编程中用作临时对象缓存,实现对象的重复使用,优化 GC,提升系统性能,但是由于不能设置对象池大小,而且放进对象池的临时对象每次 GC 运行时会被清除,所以只能用作简单的临时对象池,不能用作持久化的长连接池,比如数据库连接池、Redis 连接池

fmt.Println的实现是使用的sync.Pool

Go语言 sync.Pool 应用详解_sync.pool使用场景-CSDN博客

二、临时对象池的实现

实现对象池需要两个公共方法

Get() 从对象池取一个临时对象,没有的话会New一个

Put() 把对象放回对象池,GC的时候会被清理

1.pool的结构

临时对象池

type Pool struct {
   noCopy     noCopy
   local      unsafe.Pointer // [P]poolLocal  P的个数一般设置的跟CPU核数相等
   localSize  uintptr
   New        func() interface{}
}

禁止拷贝,使用者只能通过指针传递保证全局唯一

go vet可以检测出来

type noCopy struct{}
func (*noCopy) Lock()   {}
func (*noCopy) Unlock() {}

type poolLocal struct {
   poolLocalInternal
}

type poolLocalInternal struct {
   private interface{}
   shared  []interface{}
   sync.Mutex
}

2.取对象

从池中获取元素,如果没有就New一个,如果New为空,则返回nil

Get会为每个P维护一个本地池,P的本地池分为私有池private和共享池shared

私有池中的元素只能本地P使用,共享池中的元素可以被其它P偷走

Get会优先查找本地私有池,在查找本地共享池,最后查找其它P的共享池,如果以上全没有,调用New创建

func (p *Pool) Get() interface{} {
   if race.Enabled {
      race.Disable()
   }
   //获取本地的P的poolLocal
   l := p.pin()
   x := l.private
   l.private = nil
   runtime_procUnpin()
   if x == nil {
      //从本地共享池获取poolLocal
      //本地shared池可能被其它P访问,需加锁
      l.Lock()
      last := len(l.shared) - 1
      if last >= 0 {
         x = l.shared[last]
         l.shared = l.shared[:last]
      }
      l.Unlock()
      //从其它P的共享池获取poolLocal
      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
}

3.归还对象

优先放入本地的private池中,如果private不为空,则放入shared池

放入之前有1/4的概率被丢掉

func (p *Pool) Put(x interface{}) {
   if x == nil {
      return
   }
   if race.Enabled {
      if fastrand()%4 == 0 {
         //随机把元素扔掉
         // Randomly drop x on floor.
         return
      }
      race.ReleaseMerge(poolRaceAddr(x))
      race.Disable()
   }
   l := p.pin()
   //如果本地poolLocal的private对象是空的,则优先放入这里
   if l.private == nil {
      l.private = x
      x = nil
   }
   runtime_procUnpin()
   //如果存入private失败,放入本地poolLocal的shared数组中
   if x != nil {
      l.Lock()
      l.shared = append(l.shared, x)
      l.Unlock()
   }
   if race.Enabled {
      race.Enable()
   }
}

从其它P的共享池获取poolLocal

func (p *Pool) getSlow() (x interface{}) {
   size := atomic.LoadUintptr(&p.localSize)
   local := p.local
   // 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
}

4.对象池清除,GC之前会调用清空

var allPools []*Pool

该函数内不能分配内存且不能调用任何运行时函数

原因: 防止错误的保留整个 Pool

如果 GC 发生时,某个 goroutine 正在访问 l.shared,整个 Pool 将会保留,下次执行时将会有双倍内存

func poolCleanup() {
   for i, p := range allPools {
      allPools[i] = nil
      for i := 0; i < int(p.localSize); i++ {
         l := indexLocal(p.local, i)
         l.private = nil
         for j := range l.shared {
            l.shared[j] = nil
         }
         l.shared = nil
      }
      p.local = nil
      p.localSize = 0
   }
   allPools = []*Pool{}
}
// 清理
func runtime_registerPoolCleanup(func())

// 返回pid,并对m加锁,禁止当前p被抢占
func runtime_procPin() int

// 对m解锁, 把禁止抢占标识释放了
func runtime_procUnpin()

三、参考