为什么读源码之sync.Pool

多态薄雾
• 阅读 1117

前言

我们为什么要读源码?因为我们只有深入到实现原理,才能了解他的优势,架构和核心原理能帮助我们快速定位问题。避免重复造轮子,借鉴思想。今天我们就来看下sync.pool的源码

type Pool struct {
   noCopy noCopy
   local     unsafe.Pointer // 本地固定大小的池子。等价于每个P一个池子 [p] p是索引ID
 localSize uintptr        // 本地数组大小
 // New optionally specifies a function to generate // a value when Get would otherwise return nil. // It may not be changed concurrently with calls to Get. New func() interface{}
}
//本地P index索引
type poolLocalInternal struct {
   private interface{}   //私有对象只能被创建时的P用。
 shared  []interface{} // 共享对象 能被其他P调用
 Mutex                 // Protects shared.
}
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()
   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()
   }
}
//获取当前P的localPool
func (p *Pool) pin() *poolLocal {
   pid := runtime_procPin()
   // In pinSlow we store to localSize and then to local, here we load in opposite order.
 // Since we've disabled preemption, GC cannot happen in between. // Thus here we must observe local at least as large localSize. // We can observe a newer/larger local, it is fine (we must observe its zero-initialized-ness). s := atomic.LoadUintptr(&p.localSize) // load-acquire
 l := p.local                          // load-consume
 if uintptr(pid) < s {
      return indexLocal(l, pid)
   }
   return p.pinSlow()
}
//
func (p *Pool) pinSlow() *poolLocal {
  //重试
 // 当被锁定时不能+mutex. runtime_procUnpin()
   allPoolsMu.Lock()
   defer allPoolsMu.Unlock()
   pid := runtime_procPin()
   // poolCleanup 不会被调用 当我们被锁定时
 s := p.localSize
   l := p.local
   //当前pid小于size 使用pid去本地local索引到localPool对象
   if uintptr(pid) < s {
      return indexLocal(l, pid)
   }
   if p.local == nil {
      allPools = append(allPools, p)
   }
   // 如果GCs的时候 GOMAXPROCS变化。我们会重新分配数组 并遗弃旧的
 size := runtime.GOMAXPROCS(0)
   local := make([]poolLocal, size)
   atomic.StorePointer(&p.local, unsafe.Pointer(&local[0])) // store-release
 atomic.StoreUintptr(&p.localSize, uintptr(size))         // store-release
 return &local[pid]
}

以上就是PUT的大致流程。

//get 也是调用p.pin获取本地local.然后获取private,如果nil,则+lock 从shared查找,不然从其他P的localPool偷取。
func (p *Pool) Get() interface{} {
   if race.Enabled {
      race.Disable()
   }
   l := p.pin()//定位local
   x := l.private //私有对象
   l.private = nil //clear
   runtime_procUnpin()
   if x == nil { //私有对象为空
      l.Lock()
      last := len(l.shared) - 1 //从share尾部开始
 if last >= 0 {
         x = l.shared[last]
         l.shared = l.shared[:last]
      }
      l.Unlock()
      if x == nil {
         x = p.getSlow() //下面看slow
      }
   }
   if race.Enabled {
      race.Enable()
      if x != nil {
         race.Acquire(poolRaceAddr(x))
      }
   }
   if x == nil && p.New != nil {
      x = p.New() // 所有P的share中都没找到,那么新建
   }
   return x
}
func (p *Pool) getSlow() (x interface{}) {
   // 获取当前size
 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++ { //循环 size次
      l := indexLocal(local, (pid+i+1)%int(size)) //定位从当前P+1 %size开始,就是从当前p往后走一圈。
      l.Lock() //加锁
      last := len(l.shared) - 1
      //检查每个P的shared末尾是否存在这个值,存在就返回。
 if last >= 0 {
         x = l.shared[last]
         l.shared = l.shared[:last]
         l.Unlock()
         break
 }
      l.Unlock()
   }
   return x
}

以上是GET操作

1.14 poolCleanup

我们直接看1.14版本的 poolCleanup,上面的get,put均是12.5版本

这个Cleanup的思路很好,引入victim 和local概念,在我看来就是0/1切换思想
思路: Put新对象放在local中,Get从victim拿,拿不到再从local拿
GC的时候执行poolCleanup,先删除victim。然后将当前池子中的对象(旧对象)移到victim中。
func poolCleanup() {
   // This function is called with the world stopped, at the beginning of a garbage collection.
 // It must not allocate and probably should not call any runtime functions.
 // Because the world is stopped, no pool user can be in a // pinned section (in effect, this has all Ps pinned).
 // Drop victim caches from all pools. for _, p := range oldPools {
      p.victim = nil
      p.victimSize = 0
 }
   // Move primary cache to victim cache.
 for _, p := range allPools {
      p.victim = p.local
      p.victimSize = p.localSize
      p.local = nil
      p.localSize = 0
 }
   // The pools with non-empty primary caches now have non-empty
 // victim caches and no pools have primary caches. oldPools, allPools = allPools, nil
}
对比
我看的1.12.5 版本的sync.pool实现基于mutex来lock.保证多goroutine安全.看的最新1.14版本引入双链表 移除mutex 改善共享访问

所以我们在使用12.5版本以下的时候要注意GC引起的sync.pool的全部清空带来的毛刺。另外适合sync.pool的场景是对象频繁创建
比如 我现在有个推送任务100万人群/次。 结构体是

type Manual struct {
   core.BaseTask
   core.BaseClass
   ManualFormat *model.ManualFormat
   ManualAppId  []int
   Cfg          *baseConfig.TomlConfig
   IsAllPush    bool
}

每次都要对人群渲染。此时用sync.pool 能减少大量GC的压力。 也要注意到引发GC的两个条件.第一条,2分钟触发一次。第二条,内存达到一定阈值触发一次。

参考资料

https://mp.weixin.qq.com/s/Oc...

点赞
收藏
评论区
推荐文章
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
Wesley13 Wesley13
4年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
3A网络 3A网络
3年前
手写 Java HashMap 核心源码
手写JavaHashMap核心源码手写JavaHashMap核心源码上一章手写LinkedList核心源码,本章我们来手写JavaHashMap的核心源码。我们来先了解一下HashMap的原理。HashMa
Stella981 Stella981
4年前
Python+Selenium自动化篇
本篇文字主要学习selenium定位页面元素的集中方法,以百度首页为例子。0.元素定位方法主要有:id定位:find\_element\_by\_id('')name定位:find\_element\_by\_name('')class定位:find\_element\_by\_class\_name(''
Wesley13 Wesley13
4年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Wesley13 Wesley13
4年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Easter79 Easter79
4年前
TurnipBit开发板DIY呼吸的吃豆人教程实例
  转载请以链接形式注明文章来源(MicroPythonQQ技术交流群:157816561,公众号:MicroPython玩家汇)  0x00前言  吃豆人是耳熟能详的可爱形象,如今我们的TurnipBit也集成了这可爱的图形,我们这就让他来呼吸了~。  0x01效果展示  先一起看下最终的成品演示视频:  http:/
Stella981 Stella981
4年前
Spring 中优雅的获取泛型信息
简介Spring源码是个大宝库,我们能遇到的大部分工具在源码里都能找到,所以笔者开源的mica(https://gitee.com/596392912/mica)完全基于Spring进行基础增强,不重复造轮子。今天我要分享的是在Spring中优雅的获取泛型。获取泛型自己解析我们之前的处理方式,代码来源
Stella981 Stella981
4年前
C语言Linix服务器网络爬虫项目(一)项目初衷和网络爬虫概述
一、项目初衷和爬虫概述1.项目初衷  本人的大学毕设就是linux上用c写的一个爬虫,现在我想把它完善起来,让他像一个企业级别的项目。为了重复发明轮子来学习轮子的原理,我们不使用第三方框架(这里是说的是指通常的linux系统编程教材以外的库和接口0,当初我也是在虚拟机上跑的ubuntuputtyvim来开发)来实现。  然而我们造轮
为什么mysql不推荐使用雪花ID作为主键
作者:毛辰飞背景在mysql中设计表的时候,mysql官方推荐不要使用uuid或者不连续不重复的雪花id(long形且唯一),而是推荐连续自增的主键id,官方的推荐是auto_increment,那么为什么不建议采用uuid,使用uuid究