当前位置: 首页 > news >正文

golang Pool实战与底层实现

使用的go版本为 go1.21.2

首先我们写一个简单的Pool的使用代码

package mainimport "sync"var bytePool = sync.Pool{New: func() interface{} {b := make([]byte, 1024)return &b},
}func main() {for j := 0; j < 10; j++ {obj := bytePool.Get().(*[]byte) // 获取一个[]byte_ = objbytePool.Put(obj) // 用完再给放回去}
}

pool对象池的作用

  1. 减少内存分配: 通过池,可以减少对内存的频繁分配和释放,提高程序的内存利用率。
  2. 避免垃圾回收压力: 对象池中的对象在被使用后不会立即被释放,而是放回到池中等待复用。这有助于减轻垃圾回收的压力,因为对象可以在多次使用后才被真正释放。
  3. 提高性能: 复用对象可以避免不必要的对象创建和销毁开销,从而提高程序的性能。
    从demo上看好像没啥卵用,我们来进行一些压力测试
package mainimport ("sync""testing"
)var bytePool = sync.Pool{New: func() interface{} {b := make([]byte, 1024)return &b},
}func BenchmarkByteMake(b *testing.B) {b.ReportAllocs()b.ResetTimer()for i := 0; i < b.N; i++ {for j := 0; j < 10000; j++ {obj := make([]byte, 1024)_ = obj}}
}func BenchmarkBytePool(b *testing.B) {b.ReportAllocs()b.ResetTimer()for i := 0; i < b.N; i++ {for j := 0; j < 10000; j++ {obj := bytePool.Get().(*[]byte) // 获取一个1024长度的[]byte_ = objbytePool.Put(obj) // 用完再给放回去}}
}

看一下压测效果

可以看到执行效率高了好多倍

项目中没实际用到过,不过我们可以翻一下开源项目中是怎么用的

redis-v9 

 

Pool结构体

比较复杂有点套娃的意思

//代码位于 GOROOT/src/sync/pool.go L:49
type Pool struct {//防止Pool被复制, 君子协议,编译可以通过,某些编辑器会报waring//静态检测 go vet会出错//有兴趣可以看一下这里 https://github.com/golang/go/issues/8005#issuecomment-190753527noCopy noCopylocal     unsafe.Pointer // 本地池,对应类型[P]poolLocal P指的是 GMP中的P.ID字段localSize uintptr        // 本地池大小victim     unsafe.Pointer // 上一个周期的本地池victimSize uintptr        // 上一个周期的本地池大小New func() any // 创建对象的方法,这个需要我们自己实现
}type poolLocal struct { //本地池poolLocalInternal// 用128取模,确保结构体占据整数个缓存行,从而防止伪共享.pad [128 - unsafe.Sizeof(poolLocalInternal{})%128]byte
}type poolLocalInternal struct {private interface{} // 本地P的私有字段shared  poolChain   // 双端链表, 任何P都可以进行popTail
}//代码位于 GOROOT/src/sync/poolqueue.go L:194
type poolChain struct { // 双向队列//头部head *poolChainElt//尾部tail *poolChainElt
}type poolChainElt struct { //环状队列poolDequeue // next 由生产者原子性地写入,并由消费者原子性地读取, 从非nil转换为nil// prev 由消费者原子性地写入,并由生产者原子性地读取, 从非nil转换为nilnext, prev *poolChainElt
}//代码位于 GOROOT/src/sync/poolqueue.go L:19
type poolDequeue struct {//一个字段两个含义,高32位为头,低32位为尾部headTail uint64//环形缓存//vals[i].typ 为nil 说明该槽位为空vals []eface
}type eface struct { //类型与值typ, val unsafe.Pointer
}

Get函数

//代码位于 GOROOT/src/sync/pool.go L:127
func (p *Pool) Get() any {if race.Enabled { // 使用竞态检查race.Disable() //竞态检查 禁用}l, pid := p.pin() //获取当前P的ID 与 poolLocal 详细见下方x := l.private //看看私有属性是否存在对象,如果存在就可以直接返回l.private = nilif x == nil { ////优先从链表的头部获取,x, _ = l.shared.popHead()if x == nil {// 慢读取路径x = p.getSlow(pid)}}runtime_procUnpin() //取消 P 的禁止抢占if race.Enabled { // 使用竞态检查race.Enable() //竞态检查 启用if x != nil {race.Acquire(poolRaceAddr(x))}}if x == nil && p.New != nil { //调度new方法重新生成一个对象x = p.New()}return x
}
pin函数
 
//代码位于 GOROOT/src/sync/pool.go L:127
func (p *Pool) pin() (*poolLocal, int) {//获取P的idpid := runtime_procPin()// 原子操作获取本地池大小// 本地池s := runtime_LoadAcquintptr(&p.localSize) // load-acquirel := p.local                              // load-consumeif uintptr(pid) < s { //如果当前P.id 没有在local中越界,直接去获取值return indexLocal(l, pid), pid}return p.pinSlow() //慢获取
}func (p *Pool) pinSlow() (*poolLocal, int) {//取消P的禁止抢占runtime_procUnpin()allPoolsMu.Lock() //加锁defer allPoolsMu.Unlock()pid := runtime_procPin() //获取P的id//获取本地池的大小与本地池s := p.localSizel := p.localif uintptr(pid) < s { //如果当前P.id 没有在local中越界,直接去获取值return indexLocal(l, pid), pid}if p.local == nil { //如果local为空,将他加入到allPools中allPools = append(allPools, p)}// GOMAXPROCS在GC之间发送了变化,重新分配p.load与p.localSizesize := runtime.GOMAXPROCS(0)local := make([]poolLocal, size)atomic.StorePointer(&p.local, unsafe.Pointer(&local[0])) // store-releaseruntime_StoreReluintptr(&p.localSize, uintptr(size))     // store-releasereturn &local[pid], pid
}
getSlow函数
//代码位于 GOROOT/src/sync/pool.go L:156
func (p *Pool) getSlow(pid int) any {// 原子获取本地池大小// 本地池size := runtime_LoadAcquintptr(&p.localSize) // load-acquirelocals := p.local                            // load-consume// 尝试从别的P poolLocal尾部获取local// 这个循环的方式有点东西(pid+i+1)%int(size),优先从非pid的下标获取,最后一次是pidfor i := 0; i < int(size); i++ {l := indexLocal(locals, (pid+i+1)%int(size))if x, _ := l.shared.popTail(); x != nil {return x}}// 原子获取上一周期本地池大小size = atomic.LoadUintptr(&p.victimSize)if uintptr(pid) >= size { //如果pid大于size 说明让回收掉了return nil}locals = p.victiml := indexLocal(locals, pid)if x := l.private; x != nil {//看看私有属性是否存在对象,如果存在就可以直接返回l.private = nilreturn x}// 尝试从别的P poolLocal尾部获取localfor i := 0; i < int(size); i++ {l := indexLocal(locals, (pid+i)%int(size))if x, _ := l.shared.popTail(); x != nil {return x}}//将victimSize设置为0atomic.StoreUintptr(&p.victimSize, 0)return nil
}

 

Put函数

//代码位于 GOROOT/src/sync/pool.go L:95
func (p *Pool) Put(x any) {if x == nil { //如果写入的x为nil之间返回return}if race.Enabled { //使用竞态检查if fastrandn(4) == 0 {// Randomly drop x on floor.return}race.ReleaseMerge(poolRaceAddr(x))race.Disable() // 竞态检查 禁用}l, _ := p.pin() // 获取PoolLocalif l.private == nil { // 如果私有属性没有赋值l.private = x} else { //将x写入头l.shared.pushHead(x)}runtime_procUnpin()if race.Enabled { //使用竞态检查race.Enable() //竞态检查 启用}
}

pushHead函数解读
//代码位于 GOROOT/src/sync/poolqueue.go L:228
func (c *poolChain) pushHead(val any) {d := c.headif d == nil { //如果head为空,将head初始化为8长度的eface数组const initSize = 8 // Must be a power of 2d = new(poolChainElt)d.vals = make([]eface, initSize)c.head = dstorePoolChainElt(&c.tail, d) //将新创建的节点,当做尾节点}if d.pushHead(val) { //对象入队return}// 走到这里说明满了。可扩容为2倍newSize := len(d.vals) * 2// 扩容大小 (1 << 32) / 4 超出将这个设置为(1 << 32) / 4if newSize >= dequeueLimit { newSize = dequeueLimit}//新建poolChainElt将prev指向dd2 := &poolChainElt{prev: d}d2.vals = make([]eface, newSize)c.head = d2 //将新创建的节点,当做头节点storePoolChainElt(&d.next, d2) // 将老的节点指向,新节点d2.pushHead(val) //对象入队
}

 

延迟处理下标小技巧

package mainimport ("fmt"
)func main() {pid := 1size := 20for i := 0; i < int(size); i++ {if i == pid {continue}fmt.Println(i)}// 优化版本 pid会在最后一个打印处理for i := 0; i < size; i++ {index := (pid + i + 1) % size// 前面处理完以后直接returnfmt.Println(index)}
}

总结

我们从上面的源码分析了解Pool的数据结构、Get、Put这些基本操作原理,在项目中我们可以使用比特位来减少内存的占用,从源码分析我们得知Go官方设计不允许进行Pool复制(君子协议), 还学到了一个延迟处理下标的小技巧。

相关文章:

golang Pool实战与底层实现

使用的go版本为 go1.21.2 首先我们写一个简单的Pool的使用代码 package mainimport "sync"var bytePool sync.Pool{New: func() interface{} {b : make([]byte, 1024)return &b}, }func main() {for j : 0; j < 10; j {obj : bytePool.Get().(*[]byte) // …...

WPF使用Prism框架批量注册Page,Window,UserControl等视图组件

前言 为了提高Prism框架下面的注册视图资源的简单性和提高后期可维护性,本文将使用prism自带的通过反射来批量注册视图资源,帮助我们快速高效的完成开发任务。 我们平常注册前端视图资源,一般都是在RegisterTypes方法里面,使用IContainerRegistry 的RegisterForNavigation…...

网络安全应急响应-Server2228(环境+解析)

网络安全应急响应 任务环境说明: 服务器场景:Server2228(开放链接)用户名:root,密码:p@ssw0rd123...

[WP] ISCTF2023 Web 部分题解

圣杯战争!!! 反序列化伪协议读取 where_is_the_flag 环境变量根目录当前目录 绕进你的心里 利用正则最大回溯绕过 easy_website or select 用双写绕过 空格用/**/绕&#xff0c;报错注入 wafr codesystem(ca\t /f*) webinclude 扫描得到index.bak备份文件打开为加密的代码 写…...

uniapp之Vue3配置跨域(代理)

在uni-app中&#xff0c;我们可以使用vue.config.js文件来配置跨域&#xff08;代理&#xff09;。以下是一个示例&#xff1a; // vue.config.js module.exports {devServer: {proxy: {/api: { // 这里填写你要代理的接口前缀&#xff0c;例如/apitarget: http://localhost:…...

单片机实验(三)

前言 实验一&#xff1a;利用定时器T1的中断控制P1.7引脚输出音频信号&#xff0c;启动蜂鸣器发出一段熟悉的与众不同的具有10个音节的音乐音频。 实验二&#xff1a;使用定时器/计数器来实现一个LCD显示年、月、日、星期 、时、分、秒的电子表&#xff0c;要求时和分可以方便…...

Python 2进制按位取反

根据一checksum算法需要将一些参数按位取反 例&#xff1a;参数 13 数字13二进制为1101 [((x)) for x in str(bin(13))] [0, b, 1, 1, 0, 1] 除去0b字符串然后按位取反得到0010 [(1^int(x)) for x in str(bin(13)).replace(0b,)] [0, 0, 1, 0]然后将得到的2进制转换成十进制…...

【用Python根据用户名和手机号码生成Hash值并创建.cs .h和xlsx文件】

用Python根据用户名和手机号码生成Hash值并创建C Sharp .cs、嵌入式.h和xlsx表格文件 用Python根据用户名和手机号码生成Hash值并创建C Sharp .cs、嵌入式.h和xlsx表格文件&#xff0c;主要是为每个用户创建一个pubkey&#xff0c;并输出C Sharp C#和嵌入式 Keil的工程文件 pub…...

<Linux>(极简关键、省时省力)《Linux操作系统原理分析之存储管理(2)》(15)

[TOC](《Linux操作系统原理分析之存储管理&#xff08;2&#xff09;》&#xff08;15&#xff09; 5 存储管理5.4 分页存储管理5.4.1 纯分页存储管理a.页&#xff08;页面&#xff09;和物理块&#xff08;帧&#xff09;b. 页面大小c. 逻辑地址结构 5.5 存储扩充技术5.5.2 交…...

jdk8、jdk9中,接口的新特性

接口的老特性&#xff1a; 没有构造方法成员变量只能定义常量&#xff0c;默认三个关键字public static final只能是抽象方法&#xff0c;默认两个关键字public abstract 接口的新特性&#xff1a; jdk8 1.接口允许定义非抽象方法&#xff0c;需加入default关键字。为了解决…...

第一题-字符串拼接【第六届传智杯程序设计挑战赛解题分析详解复盘】(C/C++实现)

🚀 欢迎来到 ACM 算法题库专栏 🚀 在ACM算法题库专栏,热情推崇算法之美,精心整理了各类比赛题目的详细解法,包括但不限于ICPC、CCPC、蓝桥杯、LeetCode周赛、传智杯等等。无论您是刚刚踏入算法领域,还是经验丰富的竞赛选手,这里都是提升技能和知识的理想之地。 ✨ 经典…...

简谈oracle数据库的归档模式

一、oracle数据库归档模式简介 Oracle数据库归档模式是一种数据备份和恢复策略,它允许数据库记录所有数据库的更改操作(包括已提交和未提交的事务)并将其存储在归档日志中。这些归档日志可以用于在数据库发生故障时进行恢复,并提供点时间恢复(PITR)的能力。 在Oracle数…...

FLASK博客系列9——你想成为我的新用户吗?

距离上次发文好久好久了。 先说声抱歉&#xff0c;拖更的毛病我会改掉的。 上次我们教大家如何用后台去管理用户和新增文章&#xff0c;但始终都是单机操作&#xff0c;怎么让你的朋友也来加入你的小站呢&#xff1f;今天我们来为我们的网站增添一个新功能&#xff0c;实现用户…...

用通俗的方法讲解:大模型微调训练详细说明(附理论+实践代码)

本文内容如下 介绍了大模型训练的微调方法&#xff0c;包括prompt tuning、prefix tuning、LoRA、p-tuning和AdaLoRA等。 介绍了使用deepspeed和LoRA进行大模型训练的相关代码。 给出了petals的介绍&#xff0c;它可以将模型划分为多个块&#xff0c;每个用户的机器负责其中一…...

现代化工安全保障迎来巡查无人机新时代

当今现代化工企业呈现出规模不断扩大&#xff0c;设备逐渐趋向大型化的局面&#xff0c;由此导致化工安全生产面临日益严峻的挑战。然而&#xff0c;随着巡查无人机技术的成熟&#xff0c;这种新的高效手段正在提高化工安全检测的工作效率。 一、传统化工安全巡检存在弊端 化工…...

关于web前端通过js获取后端mysql数据库数据的一个方法

关于web前端通过js获取后端mysql数据库数据的一个方法 问题引入 关于html的教程很多&#xff0c;关于mysql的教程也很多&#xff0c;那么怎么让html展示mysql的数据呢&#xff1f; 一言以蔽之 前端通过js向后端发起一个http请求&#xff0c;后端响应这个请求并返回数据 实…...

如何下载IEEE出版社的Journal/Conference/Magazine的LaTeX/Word模板

当你准备撰写一篇学术论文或会议论文时&#xff0c;使用IEEE&#xff08;电气和电子工程师协会&#xff09;的LaTeX或Word模板是一种非常有效的方式&#xff0c;它可以帮助你确保你的文稿符合IEEE出版的要求。无论你是一名研究生生或一名资深学者&#xff0c;本教程将向你介绍如…...

京东数据运营-京东数据开放平台-鲸参谋10月粮油调味市场品牌店铺销售数据分析

鲸参谋监测的京东平台10月份料油调味市场销售数据已出炉&#xff01; 根据鲸参谋数据显示&#xff0c;今年10月份&#xff0c;京东平台粮油调味市场的销量将近4600万&#xff0c;环比增长约10%&#xff0c;同比降低约20%&#xff1b;销售额将近19亿&#xff0c;环比增长约4%&am…...

ThermalLabel SDK for .NET 13.0.23.1113 Crack

ThermalLabel SDK for .NET 是一个 .NET 典型类库&#xff0c;它允许用户和开发人员创建非常创新的条码标签并将其发布在 zebra ZPL、EPL、EPSON ESC、POS 以及 Honeywell intermec 指纹中通过在 VB.NET 或 C# 上编写 .NET 纯代码来实现热敏打印机&#xff0c;以实现项目框架的…...

[Java学习日记]网络编程

目录 一.常见的软件架构、网络编程三要素、IP 二.利用UDP发送与接收数据 三.改聊天室 四.组播案例 五.TCP通信案例 一.常见的软件架构、网络编程三要素、IP 网络编程&#xff1a;在网络通信协议下&#xff0c;不同的计算机上运行的程序进行的数据传输 在Java中可以使用java…...

【网络】每天掌握一个Linux命令 - iftop

在Linux系统中&#xff0c;iftop是网络管理的得力助手&#xff0c;能实时监控网络流量、连接情况等&#xff0c;帮助排查网络异常。接下来从多方面详细介绍它。 目录 【网络】每天掌握一个Linux命令 - iftop工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景…...

云原生核心技术 (7/12): K8s 核心概念白话解读(上):Pod 和 Deployment 究竟是什么?

大家好&#xff0c;欢迎来到《云原生核心技术》系列的第七篇&#xff01; 在上一篇&#xff0c;我们成功地使用 Minikube 或 kind 在自己的电脑上搭建起了一个迷你但功能完备的 Kubernetes 集群。现在&#xff0c;我们就像一个拥有了一块崭新数字土地的农场主&#xff0c;是时…...

【SpringBoot】100、SpringBoot中使用自定义注解+AOP实现参数自动解密

在实际项目中,用户注册、登录、修改密码等操作,都涉及到参数传输安全问题。所以我们需要在前端对账户、密码等敏感信息加密传输,在后端接收到数据后能自动解密。 1、引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId...

从零开始打造 OpenSTLinux 6.6 Yocto 系统(基于STM32CubeMX)(九)

设备树移植 和uboot设备树修改的内容同步到kernel将设备树stm32mp157d-stm32mp157daa1-mx.dts复制到内核源码目录下 源码修改及编译 修改arch/arm/boot/dts/st/Makefile&#xff0c;新增设备树编译 stm32mp157f-ev1-m4-examples.dtb \stm32mp157d-stm32mp157daa1-mx.dtb修改…...

TRS收益互换:跨境资本流动的金融创新工具与系统化解决方案

一、TRS收益互换的本质与业务逻辑 &#xff08;一&#xff09;概念解析 TRS&#xff08;Total Return Swap&#xff09;收益互换是一种金融衍生工具&#xff0c;指交易双方约定在未来一定期限内&#xff0c;基于特定资产或指数的表现进行现金流交换的协议。其核心特征包括&am…...

C# SqlSugar:依赖注入与仓储模式实践

C# SqlSugar&#xff1a;依赖注入与仓储模式实践 在 C# 的应用开发中&#xff0c;数据库操作是必不可少的环节。为了让数据访问层更加简洁、高效且易于维护&#xff0c;许多开发者会选择成熟的 ORM&#xff08;对象关系映射&#xff09;框架&#xff0c;SqlSugar 就是其中备受…...

爬虫基础学习day2

# 爬虫设计领域 工商&#xff1a;企查查、天眼查短视频&#xff1a;抖音、快手、西瓜 ---> 飞瓜电商&#xff1a;京东、淘宝、聚美优品、亚马逊 ---> 分析店铺经营决策标题、排名航空&#xff1a;抓取所有航空公司价格 ---> 去哪儿自媒体&#xff1a;采集自媒体数据进…...

全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比

目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec&#xff1f; IPsec VPN 5.1 IPsec传输模式&#xff08;Transport Mode&#xff09; 5.2 IPsec隧道模式&#xff08;Tunne…...

处理vxe-table 表尾数据是单独一个接口,表格tableData数据更新后,需要点击两下,表尾才是正确的

修改bug思路&#xff1a; 分别把 tabledata 和 表尾相关数据 console.log() 发现 更新数据先后顺序不对 settimeout延迟查询表格接口 ——测试可行 升级↑&#xff1a;async await 等接口返回后再开始下一个接口查询 ________________________________________________________…...

在Mathematica中实现Newton-Raphson迭代的收敛时间算法(一般三次多项式)

考察一般的三次多项式&#xff0c;以r为参数&#xff1a; p[z_, r_] : z^3 (r - 1) z - r; roots[r_] : z /. Solve[p[z, r] 0, z]&#xff1b; 此多项式的根为&#xff1a; 尽管看起来这个多项式是特殊的&#xff0c;其实一般的三次多项式都是可以通过线性变换化为这个形式…...