Pool

针对场景

如果你创建的对象过多,那么触发GC的频率会变高、GC的执行时间会变长,所以如果想开发一个高性能应用的话,就需要将经常使用的对象池化减少对象的创建

注意:

  1. 垃圾回收时会回收Pool里面的对象
  2. syncPool本身就是线程安全的,多个goroutine可以并发存取对象
  3. syncPool不可复制使用

源码解析

数据结构

Pool的数据结构如下:

Pool对象
local:[P]poolLocal,P大小的poolLocal数组
victim:上一个周期的local数组

poolLocalInternal对象
private interface{}:只能由相应的P使用
shared poolChain:本地P可以pushHead/popHead; 任意的P可以 popTail

poolChainElt对象
poolDequeue poolDequeue:poolDequeue is a lock-free fixed-size single-producer,
multi-consumer queue. The single producer can both push and pop from the head, and consumers can pop from the tail.单个生产者就是本地P,多个消费者就是其他的P

池对象回收(poolCleanup)

在垃圾回收开始时的STW期间执行,在 sync package init 的时候注册,由 runtime 后台执行,内容就是批量清理 allPools 里的元素。

func poolCleanup() {// 清理 oldPools 上的 victim 的元素for _, p := range oldPools {p.victim = nilp.victimSize = 0}// 把 local cache 迁移到 victim 上;// 这样就不致于让 GC 把所有的 Pool 都清空了,有 victim 再兜底以下,这样可以防止抖动;for _, p := range allPools {p.victim = p.localp.victimSize = p.localSizep.local = nilp.localSize = 0}// 清理一波所有的 allPoolsoldPools, allPools = allPools, nil
}

victim 把回收动作由一次变为了两次,这样更抗造一点。每次清理都是只有上次 local cache中留下来的对象才会被真正清理掉,当前的 cache 对象只是移到回收站(victim)。

知识小结:
每轮 GC 开始都会调用 poolCleanup 函数
使用两轮清理过程来抵抗波动,也就是 local cache 和 victim cache 配合

函数操作

Get

// the result of calling p.New.
func (p *Pool) Get() interface{} {//拿到当前P的poolLocal和pid,并且当前G固定在当前Ml, pid := p.pin()//从private拿x := l.privatel.private = nilif x == nil {//private没拿到//从链表头部拿x, _ = l.shared.popHead()if x == nil {//先尝试从其他P的localPool中偷一个Ele,如果没偷到再尝试从victim中获取Ele,之所以是这个获取顺序是为了对象在victim中呆的久一点这样垃圾回收时可以回收掉,避免浪费内存x = p.getSlow(pid)}}//M此时可抢占runtime_procUnpin()//还没拿到并且New不为nil,那么调用New函数创建对象if x == nil && p.New != nil {x = p.New()}return x
}func (p *Pool) pin() (*poolLocal, int) {//把G固定在当前P上pid := runtime_procPin()s := runtime_LoadAcquintptr(&p.localSize) // load-acquirel := p.local                              // load-consumeif uintptr(pid) < s {//获取P对应的localPoolreturn indexLocal(l, pid), pid}/*local还未初始化pinSlow 主要做以下几个事情:1. 首次 Pool 需要把自己注册进 allPools 数组;2. 按照P的个数创建poolLocal数组,赋值给local字段*/return p.pinSlow()
}

Put

func (p *Pool) Put(x interface{}) {if x == nil {return}l, _ := p.pin()//private没有放到privateif l.private == nil {l.private = xx = nil}if x != nil {//private有,就放到shared链表的头部l.shared.pushHead(x)}runtime_procUnpin()
}

常见踩坑

内存泄露

创建一个byte buffer的Pool,我们预估Pool中的byte Buffer大小在4K左右,但是拿出去的Byte Buffer在使用过程中底层的byte slice可能扩容,即使Reset了Byte Buffer中的内容大小仍然不变,这样放回Pool后,下次拿出来使用可能就不用了之前扩容的大小,造成部分内存用不到出现内存泄露

解决办法:在使用Put方法回收对象前检察一下对象的大小,如果太大就不要回收了

内存浪费

Pool中的对象可能偏大,使用的时候可能只需要一个小的buffer,那么就存在内存浪费

解决方法:将buffer池分成几层,比如小于512Byte的在一个池、小于1K的在一个池、小于4K的在一个池,按需从不同的池子拿,回收时按不同大小回收到不同的池

知名项目中实现的Pool

Pool分层
https://github.com/vitessio/vitess/tree/main/go/bucketpool

ByteBuffer Pool
https://github.com/valyala/bytebufferpool
https://github.com/oxtoacart/bpool

TCP连接池
https://github.com/fatih/pool

Worker Pool
https://github.com/gammazero/workerpool
https://github.com/ivpusic/grpool
https://github.com/dpaks/goworkers
https://github.com/alitto/pond

References

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

总结

Pool是用来解决对象重用、对象预分配的一种手段

如果你发现GC频率高、GC耗时长,并且存在大量相同类型的临时对象不断被创建、销毁,那么可以使用Pool池化手段重用这些对象

如果发现Goroutine的数量特别多,内存资源占用非常大,那么可以使用Worker Pool限制Goroutine数量

Pool:对象池源码解读相关推荐

  1. 从plugin路径中读取依赖并构造对象——Azkaban源码解读之Alert plugin实现(一)

    第一步加载类路径:azkaban.executor.AlerterHolder allAlerters 是一个HashMap ,key为String,value为Alerter mailAlerter ...

  2. Executors源码解读——创建ExecutorService线程池

    Executors源码解读--创建ExecutorService线程池 〇.[源码版本] jdk 1.8 一.线程池概述 二.线程池创建 三.Executors源码解读 newFixedThreadP ...

  3. hystrix 源码 线程池隔离_“池”的思想:从java线程池到数据库连接池的源码解读(1)...

    一. java线程池 带着问题: 线程是什么时候被创建的? 线程会一直循环取任务任务吗?怎么做的? 线程取不到任务会怎么样? 线程会被Runnable和Callable的异常干掉吗? 线程怎么干掉自己 ...

  4. 并发编程之 Executor 线程池原理与源码解读

    并发编程之 Executor 线程池原理与源码解读 线程是调度 CPU 资源的最小单位,线程模型分为 KLT 模型与 ULT 模型,JVM使用的是 KLT 模型.java线程与 OS 线程保持 1:1 ...

  5. [并发编程] - Executor框架#ThreadPoolExecutor源码解读03

    文章目录 Pre execute源码分析 addWorker()解读 Worker解读 Pre [并发编程] - Executor框架#ThreadPoolExecutor源码解读02 说了一堆结论性 ...

  6. ExecutorService源码解读

    ExecutorService源码解读 〇.[源码版本] jdk 1.8 一.ExecutorService接口详解 1.ExecutorService关闭方法概述 [举例1]代码示例 2.Execu ...

  7. java线程池_Java 并发编程 线程池源码实战

    作者 | 马启航 杏仁后端工程师.「我头发还多,你们呢?」 一.概述 笔者在网上看了好多的关于线程池原理.源码分析相关的文章,但是说实话,没有一篇让我觉得读完之后豁然开朗,完完全全的明白线程池,要么写 ...

  8. Java 并发编程 -- 线程池源码实战

    一.概述 小编在网上看了好多的关于线程池原理.源码分析相关的文章,但是说实话,没有一篇让我觉得读完之后豁然开朗,完完全全的明白线程池,要么写的太简单,只写了一点皮毛,要么就是是晦涩难懂,看完之后几乎都 ...

  9. AFNetworking 3.0 源码解读(一)之 AFNetworkReachabilityManager

    做ios开发,AFNetworking 这个网络框架肯定都非常熟悉,也许我们平时只使用了它的部分功能,而且我们对它的实现原理并不是很清楚,就好像总是有一团迷雾在眼前一样. 接下来我们就非常详细的来读一 ...

最新文章

  1. 浅析SEO优化中标签的作用?
  2. Spring Boot中的一些常用配置介绍!
  3. 【QGIS入门实战精品教程】5.1:QGIS地理坐标转火星坐标系(GCJ02)案例教程
  4. java ajax html复选框,HTMLjavaSkcriptCSSjQueryajax(九)(示例代码)
  5. JAVA爬取亚马逊的商品信息
  6. 制作uefi的kali安装盘_制作 UD + EFI + PE + KALI 启动U盘
  7. 汉字Unicode 编码大全
  8. 200行代码实现推流到直播平台
  9. 【腾讯TMQ】老司机教你如何优雅地完成一个小项目测试
  10. c语言数组输入空格回车问题
  11. 【Python基础】初识-与君初相识,犹如故人归
  12. 停止SHA1算法签名证书及时间戳服务尤其是代码签名证书,补丁介绍
  13. 谷歌浏览器设置跨域方法集合
  14. java excel 密码_Java 加密Excel文件(打开时需输入密码)
  15. Linux进程间通信——使用信号量
  16. 【原创 深度学习与TensorFlow 动手实践系列 - 4】第四课:卷积神经网络 - 高级篇...
  17. 苏大与东大计算机软件专业比较,东北大学和苏州大学哪个实力更强一些?网友:苏大强?...
  18. Ubuntu的踩坑记录
  19. 与思科交换机三层链路聚合连接 ❀ 飞塔 (Fortinet) 防火墙
  20. Android 创建单独的服务运行在后台(无界面)

热门文章

  1. 【Json】Json校验工具
  2. GCC 中的 aligned 和 packed 属性(关于地址对齐)
  3. 2022腾讯云服务器租用费用价格表(轻量和CVM精准报价)
  4. android开发发送短信,Android开发入门之发送短信
  5. 华硕重装后进入bios_重装系统以后,开机自动进bios,进不了系统怎么办
  6. Place Holder 方法
  7. java.lang.IndexOutOfBoundsExceptionInconsistency detected. Invalid view holder adapter position问题处理
  8. MyCat 学习笔记 第十五篇 . 数据分片后的迁移验证
  9. spring-cloud-stream-binder-kafka发消息指定 partitionKey
  10. 文章:Mapping regulatory variants controlling gene expression in drought response and tolerance