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

用Go实现一个无界资源池

写在文章开头

我们希望通过go语言实现一个简单的资源池,而这个资源池的资源包括但不限于:

  1. 数据库连接池
  2. 线程池
  3. 协程池
  4. 网络连接池

只要这些资源实现我们指定的关闭方法,则都可以通过我们封装的资源池进行统一管理,需要简单说明一下这个资源池的要求:

  1. 需要用户指定资源以及资源的创建方法。
  2. 当协程通过Acquire方法获取资源时,若发现当前池中有资源可以分配则直接返回,若没有足够的资源则基于传入的创建方法创建一个全新的资源分配。
  3. 支持资源释放和资源池关闭。

在这里插入图片描述

听起来很像是Java的无界线程池,接下来我们就基于这个需求实现一个版本。

在这里插入图片描述

Hi,我是 sharkChili ,是个不断在硬核技术上作死的 java coder ,是 CSDN的博客专家 ,也是开源项目 Java Guide 的维护者之一,熟悉 Java 也会一点 Go ,偶尔也会在 C源码 边缘徘徊。写过很多有意思的技术博客,也还在研究并输出技术的路上,希望我的文章对你有帮助,非常欢迎你关注我的公众号: 写代码的SharkChili

因为近期收到很多读者的私信,所以也专门创建了一个交流群,感兴趣的读者可以通过上方的公众号获取笔者的联系方式完成好友添加,点击备注 “加群” 即可和笔者和笔者的朋友们进行深入交流。

在这里插入图片描述

需求落地

给出资源池结构

我们首先需要给出资源池的结构,很明显作为一个资源池它需要有一个管理资源池的channel,为了保证多协程竞争资源的协程安全,我们还需要通过一把Mutex完成操作互斥,同时给出创建资源的工厂方法要求这个工厂方法创建的资源具备资源关闭能力:

// Pool 定义一个结构体 包含重量级锁 有缓冲区Chanel 工厂方法 连接池关闭状态
type Pool struct {m        sync.Mutexresource chan io.Closerfactory  func() (io.Closer, error)closed   bool
}

创建资源池

有个上述的定义之后,我们的创建方法就很容易实现了,只需基于外部的size和工厂方法完成Pool成员变量初始化即可:

var ErrPoolClosed = errors.New("连接池已关闭")func New(fn func() (io.Closer, error), size uint) (*Pool, error) {//判断size大小是否合法if size <= 0 {return nil, errors.New("size不合法")}//基于工厂方法和size创建资源池return &Pool{resource: make(chan io.Closer, size),factory:  fn,}, nil}

获取资源

当协程需要获取资源时,会查看当前缓冲通道是否有足够的资源,如果有则在正确运行的情况下返回出去,反之基于我们上文传入的工厂方法完成资源创建并返回:

func (p *Pool) Acquire() (io.Closer, error) {select {//如果channel有足够的资源分配则直接返回case r, ok := <-p.resource:if !ok {log.Println("连接池已关闭")return nil, ErrPoolClosed}log.Println("拿到连接池共享资源")return r, nil//基于工厂方法创建全新的资源返回出去default:log.Println("资源不足,创建新的连接资源")return p.factory()}}

释放与关闭

这里我们将资源的释放和关闭放在一起说明,在进行资源释放和关闭时我们需要考虑3个问题即:

  1. 已关闭的资源池无需归还资源。
  2. 正在关闭资源池时不可归还资源。
  3. 正在归还资源时不可关闭资源池。

所以进行这两个操作时,我们需要通过互斥锁确保两个操作互斥:

// Release 上锁 设置方法退出后解锁 查看当前连接池是否已关闭,若关闭则直接将资源关闭 ,反之select查看能否将其存入缓冲区,若可以输出入队成功,反之输出队列已满
func (p *Pool) Release(r io.Closer) {//上锁确保关闭和归还资源操作互斥p.m.Lock()//函数退出时解锁defer p.m.Unlock()//如果资源池关闭则直接将当前资源关闭销毁if p.closed {log.Println("连接池已关闭,直接销毁当前资源")r.Close()}//将连接归还,如果满了则直接关闭销毁select {case p.resource <- r:log.Println("连接归还成功")default:log.Println("连接池已满,资源直接销毁")r.Close()}}// Close 方法 上锁 设置方法退出后解锁 遍历所有资源将其关闭 然后再关闭连接池
func (p *Pool) Close() {p.m.Lock()defer p.m.Unlock()if p.closed {log.Println("连接池已关闭,直接销毁当前资源")return}//设置为关闭p.closed = true//关闭资源close(p.resource)//遍历资源池资源for r := range p.resource {r.Close()}}

测试代码与输出

最后我们给出测试代码,可以看到我们基于资源池工具类模拟数据库连接池的管理:

//设置最大协程数与资源池数为24
const maxGoroutines = 24
const poolResources = 24//创建可关闭的数据库连接
type dbConnection struct {ID int32
}
//对应的关闭方法
func (d *dbConnection) Close() error {log.Println("当前数据库连接", d.ID, "已关闭")return nil
}var idCounter int32func createConnection() (io.Closer, error) {id := atomic.AddInt32(&idCounter, 1)return &dbConnection{ID: id}, nil
}func main() {//创建maxGoroutines个WaitGroupvar wg sync.WaitGroupwg.Add(maxGoroutines)//传入createConnection方法和连接池大小poolResources创建数据库连接池p, err := pool.New(createConnection, poolResources)if err != nil {log.Println(err)}//创建24个协程获取资源for i := 0; i < maxGoroutines; i++ {go func(queryParam int) {queryData(queryParam, p)defer wg.Done()}(i)}//等待操作完成关闭连接池wg.Wait()log.Println("查询完成")p.Close()}
//queryData 基于连接池Acquire获取资源,完成后通过Release归还资源
func queryData(queryParam int, p *pool.Pool) {r, e := p.Acquire()if e != nil {log.Println(e)return}defer p.Release(r)time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond)log.Println("查询", queryParam, "使用连接", r.(*dbConnection).ID)
}

同时我们给出输出结果:

2024/05/05 23:36:10 资源不足,创建新的连接资源
2024/05/05 23:36:10 资源不足,创建新的连接资源
2024/05/05 23:36:10 资源不足,创建新的连接资源
2024/05/05 23:36:10 资源不足,创建新的连接资源
2024/05/05 23:36:10 资源不足,创建新的连接资源
2024/05/05 23:36:10 资源不足,创建新的连接资源
2024/05/05 23:36:10 资源不足,创建新的连接资源
2024/05/05 23:36:10 资源不足,创建新的连接资源
2024/05/05 23:36:10 资源不足,创建新的连接资源
2024/05/05 23:36:10 资源不足,创建新的连接资源
2024/05/05 23:36:10 资源不足,创建新的连接资源
2024/05/05 23:36:10 查询 17 使用连接 14
2024/05/05 23:36:10 连接归还成功
2024/05/05 23:36:10 查询 5 使用连接 5
2024/05/05 23:36:10 连接归还成功
2024/05/05 23:36:10 查询 3 使用连接 2
2024/05/05 23:36:10 连接归还成功
2024/05/05 23:36:10 查询 19 使用连接 19
2024/05/05 23:36:10 连接归还成功
.......

小结

以上便是笔者对于无界资源池的实现思路,希望对你有帮助。

我是 sharkchiliCSDN Java 领域博客专家开源项目—JavaGuide contributor,我想写一些有意思的东西,希望对你有帮助,如果你想实时收到我写的硬核的文章也欢迎你关注我的公众号: 写代码的SharkChili
因为近期收到很多读者的私信,所以也专门创建了一个交流群,感兴趣的读者可以通过上方的公众号获取笔者的联系方式完成好友添加,点击备注 “加群” 即可和笔者和笔者的朋友们进行深入交流。

在这里插入图片描述

参考

《go in action》

相关文章:

用Go实现一个无界资源池

写在文章开头 我们希望通过go语言实现一个简单的资源池&#xff0c;而这个资源池的资源包括但不限于: 数据库连接池线程池协程池网络连接池 只要这些资源实现我们指定的关闭方法&#xff0c;则都可以通过我们封装的资源池进行统一管理&#xff0c;需要简单说明一下这个资源池…...

Apache Seata基于改良版雪花算法的分布式UUID生成器分析2

title: 关于新版雪花算法的答疑 author: selfishlover keywords: [Seata, snowflake, UUID, page split] date: 2021/06/21 本文来自 Apache Seata官方文档&#xff0c;欢迎访问官网&#xff0c;查看更多深度文章。 关于新版雪花算法的答疑 在上一篇关于新版雪花算法的解析中…...

13、揭秘JVM垃圾回收器:面试必备知识,你掌握了吗?

13.1、前文回顾 在上一篇文章中,我们详细分析了触发Minor GC的时机,以及对象何时会从新生代迁移到老年代。我们还讨论了为了确保新生代向老年代的内存迁移安全,需要在Minor GC之前如何检查老年代的内存空间,以及在什么情况下会触发老年代的Full GC,以及老年代的垃圾回收算…...

治疗耳鸣患者案例分享第二期

“患者耳鸣20年了&#xff0c;目前耳朵没有堵或者胀的感觉&#xff0c;但是偶尔有点痒&#xff0c;平时会有头晕头胀这种情况&#xff0c;然后头晕是稍微晕炫一下。然后头疼是经常有的&#xff0c;头胀不经常。” 患者耳鸣持续20年&#xff0c;虽然耳朵没有堵或胀的感觉&#x…...

数据加密的方法

这些方法可以单独或结合使用&#xff0c;以提高数据的安全性和保护隐私。 对称加密&#xff1a;使用相同的密钥对数据进行加密和解密。常见的对称加密算法包括DES、AES和RC4。 非对称加密&#xff1a;使用一对密钥&#xff08;公钥和私钥&#xff09;对数据进行加密和解密。发…...

Android BINDER是干嘛的?

1.系统架构 2.binder 源码位置&#xff1a; 与LINUX传统IPC对比...

运维各种中间件的手动安装(非常详细)

压缩文件夹 tar -zcvf newFolder.tar.gz oldFolder 把oldFolder文件夹压缩成newFolder.tar.gz解压文件夹 tar -zxvf 压缩文件名.tar.gzlinux安装jdk &#xff08;参考 https://blog.csdn.net/qq_42269466/article/details/124079963 &#xff09; 1、创建目录存放jdk包 mkd…...

【Android】Android应用性能优化总结

AndroidApp应用性能优化总结 最近大半年的时间里&#xff0c;大部分投在了某国内新能源汽车的某款AndroidApp开发上。 由于该App是该款车上&#xff0c;常用重点应用。所以车厂对应用性能的要求比较高。 主要包括&#xff1a; 应用冷启动达到***ms。应用热(温)启动达到***ms应…...

FBA头程海运发货流程是怎样的?

FBA头程发货作为整个FBA流程的关键一环&#xff0c;更是直接影响到商品从起点到终点的流通效率和成本。其中&#xff0c;海运作为一种经济、稳定的运输方式&#xff0c;在FBA头程发货中扮演着举足轻重的角色。那么&#xff0c;FBA头程海运发货流程究竟是怎样的呢? 1、装箱与发…...

二、VLAN原理和配置

vlan不是协议&#xff0c;是一个技术&#xff0c;虚拟局域网技术&#xff0c;基于802.1q协议。 vlan&#xff08;虚拟局域网&#xff09;&#xff0c;将一个物理的局域网在逻辑上划分成多个广播域的技术。 目录 1.冲突域和广播域 概念 范围 2.以太网帧格式 3.以太网帧封装…...

stackqueue类——适配器模式 双端队列deque(C++)

接下来我们将实现 stack、queue 类的常用函数&#xff0c;其实对于 stack 和 queue 的常用函数实现可以说得上是非常简单&#xff0c;若想详细了解可以看这篇&#xff1a;栈和队列&循环队列&#xff08;C/C&#xff09;_栈和循环队列-CSDN博客&#xff1b;在本篇中我们将使…...

SpringCloud知识点梳理

1. Spring Cloud 综述 1.1 Spring Cloud 是什么 [百度百科]Spring Cloud是⼀系列框架的有序集合。它利⽤Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中⼼、消息总线、负载均衡、断路器、数据监控等,都可以⽤ Spring Boot的开发⻛格…...

【NOI】C++程序结构入门之分支结构二

文章目录 前言一、逻辑运算符1.导入2.逻辑与&#xff08;&&&#xff09;3.逻辑或&#xff08;||&#xff09;4.逻辑非&#xff08;!&#xff09; 二、例题讲解问题&#xff1a;1656. 是两位的偶数吗问题&#xff1a;1658. 游乐设施问题&#xff1a;1659. 是否含有数字5…...

web自动化系列-使用普通模式编写测试用例以及存在问题(十六)

前面已经把selenium的主要操作介绍完毕 &#xff0c;接下来我们通过编写几条测试用例感受下selenium的用法 。 1.用例需求 还是以登录为例 &#xff0c;需要实现的测试用例为 &#xff1a; case1&#xff1a;输入正确的用户名和密码进行登录case2 : 输入正确的用户名和错误的…...

VSCode 配置 Qt 开发环境

文章目录 1. 环境说明2. 配置系统环境变量 1. 环境说明 操作系统&#xff1a;Windows 11VSCode版本&#xff1a;1.88.1CMake版本&#xff1a;3.27.7Qt6版本&#xff1a;6.7.0(MinGW 11.2.0 64-bit) 2. 配置系统环境变量 自行根据自己的Qt安装路径配置 配置 MinGW 和 CMake C…...

【Jenkins】持续集成与交付 (七):Gitlab添加组、创建用户、创建项目和源码上传到Gitlab仓库

🟣【Jenkins】持续集成与交付 (七):Gitlab添加组、创建用户、创建项目和源码上传到Gitlab仓库 1、创建组2、创建用户3、将用户添加到组中4、在用户组中创建项目5、源码上传到Gitlab仓库5.1 初始化版本控制5.2 将文件添加到暂存区5.3 提交代码到本地仓库5.4 推送代码到 Git…...

L1-017 到底有多二

一个整数“犯二的程度”定义为该数字中包含2的个数与其位数的比值。如果这个数是负数&#xff0c;则程度增加0.5倍&#xff1b;如果还是个偶数&#xff0c;则再增加1倍。例如数字-13142223336是个11位数&#xff0c;其中有3个2&#xff0c;并且是负数&#xff0c;也是偶数&…...

常用语音识别开源四大工具:Kaldi,PaddleSpeech,WeNet,EspNet

无论是基于成本效益还是社区支持&#xff0c;我都坚决认为开源才是推动一切应用的动力源泉。下面推荐语音识别开源工具&#xff1a;Kaldi&#xff0c;Paddle&#xff0c;WeNet&#xff0c;EspNet。 1、最成熟的Kaldi 一个广受欢迎的开源语音识别工具&#xff0c;由Daniel Pove…...

python笔记 | 哥德巴赫猜想

哥德巴赫猜想&#xff1a;每个不小于6的偶数都可以表示成两个素数之和。 素数&#xff1a;只能被1和自身整除的正整数。就是大于1且除了1和它本身之外没有其他因数的数。例如&#xff0c;2、3、5、7、11等都是素数&#xff0c;而4、6、8、9等则不是素数。 下面这段Python代码…...

IO基础-IO多路复用基础

Java的Selector封装了底层epoll和poll的API&#xff0c;可以通过指定如下参数来调用执行的内核调用, 在Linux平台&#xff0c;如果指定 -Djava.nio.channels.spi.SelectorProvidersun.nio.ch.PollSelectorProvider 则底层调用poll&#xff0c; -Djava.nio.channels.spi.Selec…...

.Net框架,除了EF还有很多很多......

文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...

【网络安全产品大调研系列】2. 体验漏洞扫描

前言 2023 年漏洞扫描服务市场规模预计为 3.06&#xff08;十亿美元&#xff09;。漏洞扫描服务市场行业预计将从 2024 年的 3.48&#xff08;十亿美元&#xff09;增长到 2032 年的 9.54&#xff08;十亿美元&#xff09;。预测期内漏洞扫描服务市场 CAGR&#xff08;增长率&…...

使用van-uploader 的UI组件,结合vue2如何实现图片上传组件的封装

以下是基于 vant-ui&#xff08;适配 Vue2 版本 &#xff09;实现截图中照片上传预览、删除功能&#xff0c;并封装成可复用组件的完整代码&#xff0c;包含样式和逻辑实现&#xff0c;可直接在 Vue2 项目中使用&#xff1a; 1. 封装的图片上传组件 ImageUploader.vue <te…...

C# 类和继承(抽象类)

抽象类 抽象类是指设计为被继承的类。抽象类只能被用作其他类的基类。 不能创建抽象类的实例。抽象类使用abstract修饰符声明。 抽象类可以包含抽象成员或普通的非抽象成员。抽象类的成员可以是抽象成员和普通带 实现的成员的任意组合。抽象类自己可以派生自另一个抽象类。例…...

OPENCV形态学基础之二腐蚀

一.腐蚀的原理 (图1) 数学表达式&#xff1a;dst(x,y) erode(src(x,y)) min(x,y)src(xx,yy) 腐蚀也是图像形态学的基本功能之一&#xff0c;腐蚀跟膨胀属于反向操作&#xff0c;膨胀是把图像图像变大&#xff0c;而腐蚀就是把图像变小。腐蚀后的图像变小变暗淡。 腐蚀…...

Python Ovito统计金刚石结构数量

大家好,我是小马老师。 本文介绍python ovito方法统计金刚石结构的方法。 Ovito Identify diamond structure命令可以识别和统计金刚石结构,但是无法直接输出结构的变化情况。 本文使用python调用ovito包的方法,可以持续统计各步的金刚石结构,具体代码如下: from ovito…...

NPOI Excel用OLE对象的形式插入文件附件以及插入图片

static void Main(string[] args) {XlsWithObjData();Console.WriteLine("输出完成"); }static void XlsWithObjData() {// 创建工作簿和单元格,只有HSSFWorkbook,XSSFWorkbook不可以HSSFWorkbook workbook new HSSFWorkbook();HSSFSheet sheet (HSSFSheet)workboo…...

【Android】Android 开发 ADB 常用指令

查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...

comfyui 工作流中 图生视频 如何增加视频的长度到5秒

comfyUI 工作流怎么可以生成更长的视频。除了硬件显存要求之外还有别的方法吗&#xff1f; 在ComfyUI中实现图生视频并延长到5秒&#xff0c;需要结合多个扩展和技巧。以下是完整解决方案&#xff1a; 核心工作流配置&#xff08;24fps下5秒120帧&#xff09; #mermaid-svg-yP…...

加密通信 + 行为分析:运营商行业安全防御体系重构

在数字经济蓬勃发展的时代&#xff0c;运营商作为信息通信网络的核心枢纽&#xff0c;承载着海量用户数据与关键业务传输&#xff0c;其安全防御体系的可靠性直接关乎国家安全、社会稳定与企业发展。随着网络攻击手段的不断升级&#xff0c;传统安全防护体系逐渐暴露出局限性&a…...