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对象池的作用
- 减少内存分配: 通过池,可以减少对内存的频繁分配和释放,提高程序的内存利用率。
- 避免垃圾回收压力: 对象池中的对象在被使用后不会立即被释放,而是放回到池中等待复用。这有助于减轻垃圾回收的压力,因为对象可以在多次使用后才被真正释放。
- 提高性能: 复用对象可以避免不必要的对象创建和销毁开销,从而提高程序的性能。
从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 用双写绕过 空格用/**/绕,报错注入 wafr codesystem(ca\t /f*) webinclude 扫描得到index.bak备份文件打开为加密的代码 写…...

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

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

Python 2进制按位取反
根据一checksum算法需要将一些参数按位取反 例:参数 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表格文件,主要是为每个用户创建一个pubkey,并输出C Sharp C#和嵌入式 Keil的工程文件 pub…...

<Linux>(极简关键、省时省力)《Linux操作系统原理分析之存储管理(2)》(15)
[TOC](《Linux操作系统原理分析之存储管理(2)》(15) 5 存储管理5.4 分页存储管理5.4.1 纯分页存储管理a.页(页面)和物理块(帧)b. 页面大小c. 逻辑地址结构 5.5 存储扩充技术5.5.2 交…...

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

第一题-字符串拼接【第六届传智杯程序设计挑战赛解题分析详解复盘】(C/C++实现)
🚀 欢迎来到 ACM 算法题库专栏 🚀 在ACM算法题库专栏,热情推崇算法之美,精心整理了各类比赛题目的详细解法,包括但不限于ICPC、CCPC、蓝桥杯、LeetCode周赛、传智杯等等。无论您是刚刚踏入算法领域,还是经验丰富的竞赛选手,这里都是提升技能和知识的理想之地。 ✨ 经典…...

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

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

用通俗的方法讲解:大模型微调训练详细说明(附理论+实践代码)
本文内容如下 介绍了大模型训练的微调方法,包括prompt tuning、prefix tuning、LoRA、p-tuning和AdaLoRA等。 介绍了使用deepspeed和LoRA进行大模型训练的相关代码。 给出了petals的介绍,它可以将模型划分为多个块,每个用户的机器负责其中一…...

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

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

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

京东数据运营-京东数据开放平台-鲸参谋10月粮油调味市场品牌店铺销售数据分析
鲸参谋监测的京东平台10月份料油调味市场销售数据已出炉! 根据鲸参谋数据显示,今年10月份,京东平台粮油调味市场的销量将近4600万,环比增长约10%,同比降低约20%;销售额将近19亿,环比增长约4%&am…...

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

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

spring boot mybatis TypeHandler 看源码如何初始化及调用
目录 概述使用TypeHandler使用方式在 select | update | insert 中加入 配置文件中指定 源码分析配置文件指定Mapper 执行query如何转换 结束 概述 阅读此文 可以达到 spring boot mybatis TypeHandler 源码如何初始化及如何调用的。 spring boot 版本为 2.7.17,my…...

数据结构基础(带头节点的双向循环链表)
完整代码 DLinkList.hDLinkList.ctest.c DLinkList.h #pragma once #include <stdio.h> #include <stdlib.h> #include <assert.h>typedef int ElemType;// SList - 单链表 // DList - 双链表 // 带头节点的双向循环链表 - 最优链表结构,任意位置…...

STM32CubeMx+MATLAB Simulink点灯程序
STM32CubeMxMATLAB点灯程序 ✨要想实现在MATLAB Simulink环境下使用STM32,前提是已经搭建好MATLAB环境并且安装了必要的Simulink插件,以及对应的STM32支持包。 🌿需要准备一块所安装支持包支持的STM32开发板. 🔖具体支持包详情页…...

【深度学习】gan网络原理生成对抗网络
【深度学习】gan网络原理生成对抗网络 GAN的基本思想源自博弈论你的二人零和博弈,由一个生成器和一个判别器构成,通过对抗学习的方式训练,目的是估测数据样本的潜在分布并生成新的数据样本。 1.下载数据并对数据进行规范 transform tran…...

springboot参数汇总
multipart multipart.enabled 开启上传支持(默认:true) multipart.file-size-threshold: 大于该值的文件会被写到磁盘上 multipart.location 上传文件存放位置 multipart.max-file-size最大文件大小 multipart.max-request-size 最大请求…...

【算法刷题】Day9
文章目录 611. 有效三角形的个数题干:题解:代码: LCR 179. 查找总价格为目标值的两个商品题干:题解:代码: 1137. 第 N 个泰波那契数题干:原理:1、状态表示(dp表里面的值所…...

LangChain的函数,工具和代理(三):LangChain中轻松实现OpenAI函数调用
在我之前写的两篇博客中:OpenAI的函数调用,LangChain的表达式语言(LCEL)中介绍了如何利用openai的api来实现函数调用功能,以及在langchain中如何实现openai的函数调用功能,在这两篇博客中,我们都需要手动去创建一个结构比较复杂的函数描述变量…...

WiFi概念介绍
WiFi概念介绍 1. 什么是WLAN2. 什么是Wi-Fi3. Wi-Fi联盟4. WLAN定义范围5. WiFi协议体系6. 协议架构7. WiFi技术的发展7.1 IEEE802.117.2 802.11标准和补充 8. 术语 1. 什么是WLAN Wireless Local Area Network,采用802.11无线技术进行互连的一组计算机和相关设备。…...

如何优雅的进行业务分层
1.什么是应用分层 说起应用分层,大部分人都会认为这个不是很简单嘛 就controller,service, mapper三层。 看起来简单,很多人其实并没有把他们职责划分开,在很多代码中,controller做的逻辑比service还多,service往往当…...

C++的std命名空间
总以为自己懂了,可是仔细想想,多问自己几个问题,发现好像又不是很清楚 命名空间(Namespace)是C中一种用于解决命名冲突问题的机制,它能够将全局作用域划分为若干个不同的区域,每个区域内可以有…...