当前位置: 首页 > 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…...

多模态2025:技术路线“神仙打架”,视频生成冲上云霄

文&#xff5c;魏琳华 编&#xff5c;王一粟 一场大会&#xff0c;聚集了中国多模态大模型的“半壁江山”。 智源大会2025为期两天的论坛中&#xff0c;汇集了学界、创业公司和大厂等三方的热门选手&#xff0c;关于多模态的集中讨论达到了前所未有的热度。其中&#xff0c;…...

DockerHub与私有镜像仓库在容器化中的应用与管理

哈喽&#xff0c;大家好&#xff0c;我是左手python&#xff01; Docker Hub的应用与管理 Docker Hub的基本概念与使用方法 Docker Hub是Docker官方提供的一个公共镜像仓库&#xff0c;用户可以在其中找到各种操作系统、软件和应用的镜像。开发者可以通过Docker Hub轻松获取所…...

java 实现excel文件转pdf | 无水印 | 无限制

文章目录 目录 文章目录 前言 1.项目远程仓库配置 2.pom文件引入相关依赖 3.代码破解 二、Excel转PDF 1.代码实现 2.Aspose.License.xml 授权文件 总结 前言 java处理excel转pdf一直没找到什么好用的免费jar包工具,自己手写的难度,恐怕高级程序员花费一年的事件,也…...

【大模型RAG】Docker 一键部署 Milvus 完整攻略

本文概要 Milvus 2.5 Stand-alone 版可通过 Docker 在几分钟内完成安装&#xff1b;只需暴露 19530&#xff08;gRPC&#xff09;与 9091&#xff08;HTTP/WebUI&#xff09;两个端口&#xff0c;即可让本地电脑通过 PyMilvus 或浏览器访问远程 Linux 服务器上的 Milvus。下面…...

【HTML-16】深入理解HTML中的块元素与行内元素

HTML元素根据其显示特性可以分为两大类&#xff1a;块元素(Block-level Elements)和行内元素(Inline Elements)。理解这两者的区别对于构建良好的网页布局至关重要。本文将全面解析这两种元素的特性、区别以及实际应用场景。 1. 块元素(Block-level Elements) 1.1 基本特性 …...

Android Bitmap治理全解析:从加载优化到泄漏防控的全生命周期管理

引言 Bitmap&#xff08;位图&#xff09;是Android应用内存占用的“头号杀手”。一张1080P&#xff08;1920x1080&#xff09;的图片以ARGB_8888格式加载时&#xff0c;内存占用高达8MB&#xff08;192010804字节&#xff09;。据统计&#xff0c;超过60%的应用OOM崩溃与Bitm…...

Swagger和OpenApi的前世今生

Swagger与OpenAPI的关系演进是API标准化进程中的重要篇章&#xff0c;二者共同塑造了现代RESTful API的开发范式。 本期就扒一扒其技术演进的关键节点与核心逻辑&#xff1a; &#x1f504; 一、起源与初创期&#xff1a;Swagger的诞生&#xff08;2010-2014&#xff09; 核心…...

零基础在实践中学习网络安全-皮卡丘靶场(第九期-Unsafe Fileupload模块)(yakit方式)

本期内容并不是很难&#xff0c;相信大家会学的很愉快&#xff0c;当然对于有后端基础的朋友来说&#xff0c;本期内容更加容易了解&#xff0c;当然没有基础的也别担心&#xff0c;本期内容会详细解释有关内容 本期用到的软件&#xff1a;yakit&#xff08;因为经过之前好多期…...

佰力博科技与您探讨热释电测量的几种方法

热释电的测量主要涉及热释电系数的测定&#xff0c;这是表征热释电材料性能的重要参数。热释电系数的测量方法主要包括静态法、动态法和积分电荷法。其中&#xff0c;积分电荷法最为常用&#xff0c;其原理是通过测量在电容器上积累的热释电电荷&#xff0c;从而确定热释电系数…...

Kafka入门-生产者

生产者 生产者发送流程&#xff1a; 延迟时间为0ms时&#xff0c;也就意味着每当有数据就会直接发送 异步发送API 异步发送和同步发送的不同在于&#xff1a;异步发送不需要等待结果&#xff0c;同步发送必须等待结果才能进行下一步发送。 普通异步发送 首先导入所需的k…...