go语言 Pool实现资源池管理数据库连接资源或其他常用需要共享的资源

go Pool

Pool用于展示如何使用有缓冲的通道实现资源池,来管理可以在任意数量的goroutine之间共享及独立使用的资源。这种模式在需要共享一组静态资源的情况(如共享数据库连接或者内存缓冲区)下非 常有用。如果goroutine需要从池里得到这些资源中的一个,它可以从池里申请,使用完后归还到资源池里。

按照上面的要求,整个资源池的要求和框架图梳理如下:
在这里插入图片描述

  1. 资源池能够根据工厂函数创建多个,也就是资源池要是个通用的不能只针对一种资源的资源池
  2. 资源池要有上限
  3. 资源池需要保证多线程安全
  4. 资源池在回收资源时,如果资源已经超过资源池上限不能放到资源池里面,需要直接释放Close掉

提到池子,channel天然就是一个池子,而且channel是多线程安全的,所以可以直接使用管道来安全的共享资源。文章按照如下四个方面对Pool资源池的设计过程以及使用进行剖析。
在这里插入图片描述

数据结构

既然确定了使用管道进行资源的共享,那么Pool里面就不能少了管道。

resources chan io.Closer

这里我们定义一个类型为io.Closer的chan,因为管道中的资源都需要再不使用时清理资源,因此这里所有传入到管道的资源都需要实现io.Closer接口。

向管道中放入资源和从管道中取出资源是多线程安全的,但是打开管道和关闭管道本身又是线程不安全的,因此这里需要对管道的创建和关闭进行保护, 这里采用sync.Mutex互斥锁进行保护。

mutex sync.Mutex

除了管道,Pool还需要一个资源创建函数,用于在资源池里创建资源。

factory func() (io.Closer, error)

factory用于创建资源,返回一个实现了io.Closer的对象,表示创建的资源。

最后,还需要一个原子变量用来表示Pool的状态。

closed bool

atomic包提供了一些原子操作,包括对bool变量的设置。

错误处理

当向一个已经关闭的Pool请求资源时,需要返回一个错误提醒用户该Pool已经关闭。

var ErrPoolClosed = errors.New("Pool has been closed.")

方法实现

创建Pool

调用New方法创建Pool,需要提供一个资源创建函数,以及资源池中初始化的资源数量。

// New 创建一个新的池子,这个池子会调用factory创建资源,并可设置资源上限
func New(fn func() (io.Closer, error), size uint) (*Pool, error) {if size <= 0 {return nil, errors.New("size value too small")}return &Pool{factory:   fn,resources: make(chan io.Closer, size),}, nil
}

获取资源

既然是资源池,那么就要提供获取资源的方法,这里采用Acquire作为获取资源的方法。资源获取时有两种情况:

  1. 如果有空闲资源,就直接返回该资源。
  2. 如果没有空闲资源,就调用factory创建一个新资源。

// Acquire 从池子中获取一个资源
func (p *Pool) Acquire() (io.Closer, error) {select {// 检查是否有空闲资源case r, ok := <-p.resources:if !ok {return nil, ErrPoolClosed}return r, nil//	因为没有空闲资源,所以创建一个新资源default:if p.factory == nil {return nil, ErrPoolClosed}return p.factory()}
}

这里用来select的一个技巧将阻塞的资源变成非阻塞的分支。当向一个空管道中获取资源时,会阻塞住,但是通过select可以在通道阻塞的情况下执行default分支,从而将阻塞的操作转换为非阻塞。

释放资源

当释放资源时也存在两种情况:

  1. 如果资源池已经关闭,就直接关闭该资源。
  2. 如果资源池没有满,就直接将资源放回资源池。
// Release 将一个使用后的资源放回池子里面
func (p *Pool) Release(r io.Closer) {// Secure this operation with the Close operation.p.m.Lock()defer p.m.Unlock()// If the pool is closed, discard the resource.if p.closed {err := r.Close()if err != nil {return}return}// select能将所有阻塞的资源通过默认分支变成非阻塞的分支select {// 试图将资源放回资源池case p.resources <- r:log.Println("Release:", "In Queue")// 如果资源池已经满了,就关闭这个资源default:log.Println("Release:", "Closing")err := r.Close()if err != nil {return}}
}

关闭资源池Pool

当资源池使用完毕时需要及时关闭资源池,并释放掉所有的资源。

// Close 关闭资源池
func (p *Pool) Close() {// 保证多线程安全p.m.Lock()defer p.m.Unlock()// 如果资源池已经关闭,就不用再关闭了if p.closed {return}// 关闭资源池并释放所有资源close(p.resources)p.closed = true// 通道即使关闭了,也能通过range将通道中的资源全部接收一遍for r := range p.resources {err := r.Close()if err != nil {return}}
}

按照上述步骤将资源池的视线示例汇总如下

// Package pool 管理用户资源的池子
package poolimport ("errors""io""log""sync"
)// 说明go 1.6之后,自带sync.Pool用来管理资源// Pool 管理用户资源的池子,可以在多个goroutine之间共享资源
type Pool struct {m         sync.Mutexresources chan io.Closerfactory   func() (io.Closer, error)closed    bool
}// ErrPoolClosed 表示(Acquire)了一个已经关闭的池子
var ErrPoolClosed = errors.New("pool has been closed")// New 创建一个新的池子,这个池子会调用factory创建资源,并可设置资源上限
func New(fn func() (io.Closer, error), size uint) (*Pool, error) {if size <= 0 {return nil, errors.New("size value too small")}return &Pool{factory:   fn,resources: make(chan io.Closer, size),}, nil
}// Acquire 从池子中获取一个资源
func (p *Pool) Acquire() (io.Closer, error) {select {// 检查是否有空闲资源case r, ok := <-p.resources:if !ok {return nil, ErrPoolClosed}return r, nil//	因为没有空闲资源,所以创建一个新资源default:if p.factory == nil {return nil, ErrPoolClosed}return p.factory()}
}// Release 将一个使用后的资源放回池子里面
func (p *Pool) Release(r io.Closer) {// Secure this operation with the Close operation.p.m.Lock()defer p.m.Unlock()// If the pool is closed, discard the resource.if p.closed {err := r.Close()if err != nil {return}return}// select能将所有阻塞的资源通过默认分支变成非阻塞的分支select {// 试图将资源放回资源池case p.resources <- r:log.Println("Release:", "In Queue")// 如果资源池已经满了,就关闭这个资源default:log.Println("Release:", "Closing")err := r.Close()if err != nil {return}}
}// Close 关闭资源池
func (p *Pool) Close() {// 保证多线程安全p.m.Lock()defer p.m.Unlock()// 如果资源池已经关闭,就不用再关闭了if p.closed {return}// 关闭资源池并释放所有资源close(p.resources)p.closed = true// 通道即使关闭了,也能通过range将通道中的资源全部接收一遍for r := range p.resources {err := r.Close()if err != nil {return}}
}

调用示例

// 这个示例展示了Pool的使用,其实新版本的go里面已经内置了资源池,这里只是为了演示如何使用自己实现的Pool
package mainimport ("io""log""math/rand""sync""sync/atomic""time""patterns/pool"
)const (maxGoroutines   = 25 // 协程数量.pooledResources = 2  // 资源池的大小
)// 数据库连接池资源信息.
type dbConnection struct {ID int32
}// Close 实现 io.Closer 接口 
func (dbConn *dbConnection) Close() error {log.Println("Close: Connection", dbConn.ID)return nil
}// idCounter 为每个连接生成一个唯一的ID
var idCounter int32// createConnection 连接创建工厂函数
func createConnection() (io.Closer, error) {id := atomic.AddInt32(&idCounter, 1)log.Println("Create: New Connection", id)return &dbConnection{id}, nil
}func main() {// 协程同步var wg sync.WaitGroupwg.Add(maxGoroutines)// 创建资源池.p, err := pool.New(createConnection, pooledResources)if err != nil {log.Println(err)}// 资源池性能模拟测试.for query := 0; query < maxGoroutines; query++ {// 这里将query作为参数传入,是为了避免闭包时引用导致所有协程都拿到的是同一个query的值go func(q int) {performQueries(q, p)wg.Done()}(query)}// 等待所有协程结束wg.Wait()// 关闭资源池log.Println("Shutdown Program.")p.Close()
}// performQueries 测试资源池的查询表现
func performQueries(query int, p *pool.Pool) {// 获取一个连接池连接conn, err := p.Acquire()if err != nil {log.Println(err)return}// 将连接信息放回连接池defer p.Release(conn)// 使用延迟模拟查询事件time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond)log.Printf("Query: QID[%d] CID[%d]\n", query, conn.(*dbConnection).ID)
}

附录

  1. 数据来源-《go语言实战》
  2. 代码仓库:gitee pool

在这里插入图片描述

如果感觉文章对你有用欢迎点赞,评论和关注,谢谢!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/63064.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LeetCode70. 爬楼梯(2024冬季每日一题 24)

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢&#xff1f; 示例 1&#xff1a; 输入&#xff1a;n 2 输出&#xff1a;2 解释&#xff1a;有两种方法可以爬到楼顶。 1. 1 阶 1 阶 2. 2 阶 示例 2&#xf…

Android 系统之Init进程分析

1、Init进程流程 2、Init细节逻辑 2.1 Init触发shutdown init进程触发系统重启是一个很合理的逻辑&#xff0c;为什么合理&#xff1f; init进程是android世界的一切基石&#xff0c;如果android世界的某些服务或者进程出现异常&#xff0c;那么会导致整个系统无法正常使用…

用micropython 操作stm32f4单片机的定时器实现蜂鸣器驱动

import pyb import time # 初始化引脚和定时器通道作为PWM输出 # 注意&#xff1a;这里我们假设您使用的是支持PWM的引脚和定时器 # 在不同的MicroPython板上&#xff0c;支持的引脚和定时器可能不同 # 请查阅您的板的文档以确认正确的引脚和定时器 buzzer_pin pyb.Pin(PD15,…

长沙市的科技查新单位

1、中南大学图书馆科技查新站&#xff1a; 中南大学图书馆科技查新站成立于2003年12月&#xff0c;中南大学图书馆科技查新站作为教育部首批批准的科技查新工作站之一&#xff0c;具备了在全国范围内开展科技查新工作的专业资质。 2、湖南大学科技查新站&#xff1a; 湖南大学…

java基础语法光速入门

前言 欢迎来到我的博客 个人主页:北岭敲键盘的荒漠猫-CSDN博客 本文整理Java的基础语法部分 适合有编程基础的人快点掌握语法使用 没学过一两门语言的话。。还是不建议看了 极致的浓缩没有一点解释 注释 单行注释 // 多行注释 /**/ 数据类型 布尔型:true false 整型:int,lon…

【redis】集群详解

redis集群 一、集群的概念二、数据分片算法2.1哈希求余算法2.2一致性哈希算法2.3哈希槽分区算法 三、集群的搭建3.1配置docker-compose.yml文件3.2配置generate.sh脚本文件3.3构建redis集群3.4简单测试redis集群 四、故障处理流程4.1故障判定4.2故障转移 五、集群扩容 一、集群…

Linux | Linux的开机启动流程

对于linux系统的初学者来说&#xff0c;理解并掌握linux系统启动流程能够使你够深入的理解linux系统&#xff0c;还可以通过系统的启动过程来分析问题解决问题。 Linux开机启动的流程如下图 power on 开机 post自检&#xff08;检查一部分大的硬件&#xff09; BIOS&#xf…

Scala的模式匹配(5)

package hfd.test32 //匹配 变量的类型 object Test34_4 {def main(args: Array[String]): Unit {val i1val j:Double1.2val b:Booleanfalseval x:Anybx match {case _:Int>println(s"当前是Int")case _:Double>println(s"当前是Double")case a:Boo…

TiDB如何保证数据一致性

1. 分布式事务协议 TiDB 采用了类似 Google Percolator 的分布式事务协议来处理分布式事务。这个协议基于两阶段提交&#xff08;2PC&#xff09;的思想&#xff0c;但进行了优化和改进&#xff0c;以适应分布式环境的特殊需求。在 TiDB 中&#xff0c;当一个事务需要跨多个节…

【Maven系列】深入解析 Maven 常用命令

前言 在当今的软件开发过程中&#xff0c;项目管理是至关重要的一环。项目管理包括了项目构建、依赖管理以及发布部署等诸多方面。而在Java生态系统中&#xff0c;Maven已经成为了最受欢迎的项目管理工具之一。Maven 是一套用于构建、依赖管理和项目管理的工具&#xff0c;主要…

DBA面试题-1

面临失业&#xff0c;整理一下面试题&#xff0c;找下家继续搬砖 主要参考&#xff1a;https://www.csdn.net/?spm1001.2101.3001.4476 略有修改 一、mysql有哪些数据类型 1&#xff0c; 整形 tinyint,smallint,medumint,int,bigint&#xff1b;分别占用1字节、2字节、3字节…

【Rust WebAssembly 入门实操遇到的问题】

Rust WebAssembly 入门实操遇到的问题 什么是WebAssembly跟着教程走wasm-pack build error总结 什么是WebAssembly WebAssembly&#xff08;简称Wasm&#xff09;是一种基于堆栈的虚拟机的二进制指令 格式。Wasm 被设计为编程语言的可移植编译目标&#xff0c;支持在 Web 上部…

数据挖掘之数据预处理

​​​​​​​ 引言 数据挖掘是从大量数据中提取有用信息和知识的过程。在这个过程中&#xff0c;数据预处理是不可或缺的关键步骤。数据预处理旨在清理和转换数据&#xff0c;以提高数据质量&#xff0c;从而为后续的数据挖掘任务奠定坚实的基础。由于现实世界中的数据通常…

21个Python脚本自动执行日常任务(1)

引言 作为编程领域摸爬滚打超过十年的老手&#xff0c;我深刻体会到&#xff0c;自动化那些重复性工作能大大节省我们的时间和精力。 Python以其简洁的语法和功能强大的库支持&#xff0c;成为了编写自动化脚本的首选语言。无论你是专业的程序员&#xff0c;还是希望简化日常工…

C++ 游戏开发:开启游戏世界的编程之旅(2)

三、游戏输入处理 &#xff08;一&#xff09;键盘输入处理 在游戏中&#xff0c;玩家通过键盘输入来控制角色的行动。我们需要在游戏循环中不断检测键盘事件&#xff0c;并根据不同的按键按下或松开状态来执行相应的操作。例如&#xff0c;在 SDL 中&#xff0c;可以这样处理…

重生归来之正确使用gcc.md

environment gcc --version g --version gdb --versionsudo apt update sudo apt install gcc g build-essential gdb cmake 项目目录结构 MyProject/ │ ├── CMakeLists.txt # CMake 配置文件 ├── src/ │ └── main.cpp # 源代码 ├── build/ …

探索3D世界:使用 lib3ds 读取和解析 3DS 文件

在3D图形开发中&#xff0c;读取和解析3DS文件是创建和渲染3D场景的第一步。3DS&#xff08;3D Studio&#xff09;文件格式是一种广泛使用的3D模型文件格式&#xff0c;它包含了多种类型的数据&#xff0c;用于描述3D场景中的物体、材质、相机、灯光和动画等。lib3ds 是一个开…

从 HTML 到 CSS:开启网页样式之旅(五)—— CSS盒子模型

从 HTML 到 CSS&#xff1a;开启网页样式之旅&#xff08;五&#xff09;—— CSS盒子模型 前言一、盒子模型的组成margin&#xff08;外边距&#xff09;&#xff1a;border&#xff08;边框&#xff09;&#xff1a;padding&#xff08;内边距&#xff09;&#xff1a;conten…

注解 实现原理 详解

Java 注解实现原理详解 注解&#xff08;Annotation&#xff09;是 Java 提供的一种元数据机制&#xff0c;用于为代码元素&#xff08;类、方法、字段、参数等&#xff09;添加额外的信息。注解不会直接影响程序逻辑&#xff0c;但可以通过 工具&#xff08;如编译器、运行时…

使用Feign远程调用丢失请求头问题

在使用Feign进行远程调用时&#xff0c;当前服务是能拿到请求头信息的&#xff0c;请求头包含有登录认证Cookie等重要信息&#xff0c;但是在调用远程服务时&#xff0c;远程服务却拿不到请求头信息&#xff0c;因为使用Feign进行远程调用实际上是发起新的Request请求了&#x…