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

- 资源池能够根据工厂函数创建多个,也就是资源池要是个通用的不能只针对一种资源的资源池
- 资源池要有上限
- 资源池需要保证多线程安全
- 资源池在回收资源时,如果资源已经超过资源池上限不能放到资源池里面,需要直接释放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作为获取资源的方法。资源获取时有两种情况:
- 如果有空闲资源,就直接返回该资源。
- 如果没有空闲资源,就调用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分支,从而将阻塞的操作转换为非阻塞。
释放资源
当释放资源时也存在两种情况:
- 如果资源池已经关闭,就直接关闭该资源。
- 如果资源池没有满,就直接将资源放回资源池。
// 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)
}
附录
- 数据来源-《go语言实战》
- 代码仓库:gitee pool

如果感觉文章对你有用欢迎点赞,评论和关注,谢谢!
相关文章:
go语言 Pool实现资源池管理数据库连接资源或其他常用需要共享的资源
go Pool Pool用于展示如何使用有缓冲的通道实现资源池,来管理可以在任意数量的goroutine之间共享及独立使用的资源。这种模式在需要共享一组静态资源的情况(如共享数据库连接或者内存缓冲区)下非 常有用。如果goroutine需要从池里得到这些资…...
mysql一个事务最少几次IO操作
事务的IO操作过程 开始事务:用户发起一个事务,例如执行START TRANSACTION;,此时事务开始。读取和修改数据:用户读取和修改数据时,InnoDB首先从Buffer Pool查找所需的数据页。如果数据页不在Buffer Pool中,…...
运输层总结
运输层协议:端到端协议 面向连接的传输控制协议 TCP无连接的用户数据报协议 UDP - 主要任务:为相 互通信的应用进程 提供 逻辑通信服务 - 屏蔽:运输层向高层用户 屏蔽 了下面网络核心的细节(如网络拓扑、所采用 的路由选择协议等…...
【嵌套查询】.NET开源 ORM 框架 SqlSugar 系列
.NET开源 ORM 框架 SqlSugar 系列 【开篇】.NET开源 ORM 框架 SqlSugar 系列【入门必看】.NET开源 ORM 框架 SqlSugar 系列【实体配置】.NET开源 ORM 框架 SqlSugar 系列【Db First】.NET开源 ORM 框架 SqlSugar 系列【Code First】.NET开源 ORM 框架 SqlSugar 系列【数据事务…...
React 前端框架1
一、React 简介 (一)什么是 React React 是一个用于构建用户界面的 JavaScript 库,由 Facebook 开源并维护。它采用了组件化的开发思想,允许开发者将复杂的 UI 拆分成一个个独立、可复用的小组件,就如同搭积木一般&am…...
【真正离线安装】Adobe Flash Player 32.0.0.156 插件离线安装包下载(无需联网安装)
网上很多人声称并提供的flash离线安装包是需要联网才能安装成功的,其实就是在线安装包,而这里提供的是真正的离线安装包,无需联网即可安装成功。 点击下面地址下载离线安装包: Adobe Flash Player 32.0.0.156 for IE Adobe Fla…...
数据采集时,不同地区的动态IP数据质量有什么差异?
在数据采集的广阔世界中,动态IP扮演着至关重要的角色。它们不仅帮助我们突破地域限制,还能够提供多样化的数据来源。但是,不同地区的动态IP在数据质量上是否存在差异呢?本文将探讨这一问题,并为您提供实用的见解。 动…...
【Python爬虫五十个小案例】爬取猫眼电影Top100
博客主页:小馒头学python 本文专栏: Python爬虫五十个小案例 专栏简介:分享五十个Python爬虫小案例 🐍引言 猫眼电影是国内知名的电影票务与资讯平台,其中Top100榜单是影迷和电影产业观察者关注的重点。通过爬取猫眼电影Top10…...
等保测评和 ISO27001 都是信息保护,区别是什么?
ISO27001 和等级保护(等保)都是信息安全领域重要的标准和制度,但它们在多个方面存在区别: 定义和性质 ISO27001 它是国际标准化组织(ISO)发布的信息安全管理体系标准,其目的是帮助组织建立、实…...
Linux系统编程之进程创建
概述 在Linux系统中,通过创建新的进程,我们可以实现多任务处理、并发执行和资源隔离等功能。创建进程的主要方法为:fork、vfork、clone。下面,我们将分别进行介绍。 fork fork是最常用的创建新进程的方法。当一个进程调用fork时&a…...
JAVA-IO
目录 IO流 一 字节流 1 FileOutStream 1 书写: 2 换行书写与续写: 2 FileInputStream 1 读取数据 2 循环读取: 二 字符流 1 FileReader 1 空参的read()方法读取数据: 2 有参的read()方法读取数据: 3 指定字…...
动态系统特征分析:特征向量、特征值、频率与阻尼比、参与因子计算方法
特征值和特征向量在动态系统分析中是核心工具,广泛用于电力系统小信号稳定性、机械系统模态分析等领域。以下详细介绍计算方法及应用。 1. 求解特征值与特征向量 对于一个 n n n\times n nn的系统矩阵 A A A: 右特征向量与特征值 特征值( λ \lambd…...
乐鑫发布 esp-iot-solution v2.0 版本
今天,乐鑫很高兴地宣布,esp-iot-solution v2.0 版本已经发布,release/v2.0 分支下的正式版本组件将为用户提供为期两年的 Bugfix 维护(直到 2027.01.25 ESP-IDF v5.3 EOL)。该版本将物联网开发中常用的功能进行了分类整…...
动态代理如何加强安全性
在当今这个信息爆炸、网络无孔不入的时代,我们的每一次点击、每一次浏览都可能留下痕迹,成为潜在的安全隐患。如何在享受网络便利的同时,有效保护自己的隐私和信息安全,成为了每位网络使用者必须面对的重要课题。动态代理服务器&a…...
Flutter 之 InheritedWidget
InheritedWidget 是 Flutter 框架中的一个重要类,用于在 Widget 树中共享数据。它是 Flutter 中数据传递和状态管理的基础之一。通过 InheritedWidget,你可以让子 Widget 在不需要显式传递数据的情况下,访问祖先 Widget 中的数据。这种机制对…...
AI 助力开发新篇章:云开发 Copilot 深度体验与技术解析
本文 一、引言:技术浪潮中的个人视角1.1 AI 和低代码的崛起1.2 为什么选择云开发 Copilot? 二、云开发 Copilot 的核心功能解析2.1 自然语言驱动的低代码开发2.1.1 自然语言输入示例2.1.2 代码生成的模块化支持 2.2 实时预览与调整2.2.1 实时预览窗口功能…...
MyBatis-Plus介绍及基本使用
文章目录 概述介绍MyBatis-Plus 常用配置分页插件配置类注解配置 快速入门maven 依赖编写配置文件编写启动类编写 MybatisPlus 配置类 代码生成器:MybatisPlusGeneratormaven依赖代码生成器核心类 概述 介绍 MyBatis-Plus(简称 MP)是一个 M…...
SpringBoot 整合 Avro 与 Kafka
优质博文:IT-BLOG-CN 【需求】:生产者发送数据至 kafka 序列化使用 Avro,消费者通过 Avro 进行反序列化,并将数据通过 MyBatisPlus 存入数据库。 一、环境介绍 【1】Apache Avro 1.8;【2】Spring Kafka 1.2…...
支持JT1078和GB28181的流媒体服务器-LKM启动配置文件参数说明
流媒体服务器地址:https://github.com/lkmio/lkm GB28181信令,模拟多个国标设备工具:https://github.com/lkmio/gb-cms 文章目录 gop_cachegop_buffer_sizeprobe_timeoutwrite_timeoutmw_latencylisten_ippublic_ipidle_timeoutreceive_timeo…...
什么是隐式类型转换?隐式类型转换可能带来哪些问题? 显式类型转换(如强制类型转换)有哪些风险?
C 中的隐式类型转换 定义:在 C 中,隐式类型转换是指由编译器自动执行的类型转换,不需要程序员显式地进行操作。这种转换在很多情况下会自动发生,比如在表达式求值、函数调用传参等过程中。常见场景 算术运算中的转换:…...
网络六边形受到攻击
大家读完觉得有帮助记得关注和点赞!!! 抽象 现代智能交通系统 (ITS) 的一个关键要求是能够以安全、可靠和匿名的方式从互联车辆和移动设备收集地理参考数据。Nexagon 协议建立在 IETF 定位器/ID 分离协议 (…...
React 第五十五节 Router 中 useAsyncError的使用详解
前言 useAsyncError 是 React Router v6.4 引入的一个钩子,用于处理异步操作(如数据加载)中的错误。下面我将详细解释其用途并提供代码示例。 一、useAsyncError 用途 处理异步错误:捕获在 loader 或 action 中发生的异步错误替…...
基于服务器使用 apt 安装、配置 Nginx
🧾 一、查看可安装的 Nginx 版本 首先,你可以运行以下命令查看可用版本: apt-cache madison nginx-core输出示例: nginx-core | 1.18.0-6ubuntu14.6 | http://archive.ubuntu.com/ubuntu focal-updates/main amd64 Packages ng…...
Python实现prophet 理论及参数优化
文章目录 Prophet理论及模型参数介绍Python代码完整实现prophet 添加外部数据进行模型优化 之前初步学习prophet的时候,写过一篇简单实现,后期随着对该模型的深入研究,本次记录涉及到prophet 的公式以及参数调优,从公式可以更直观…...
【论文笔记】若干矿井粉尘检测算法概述
总的来说,传统机器学习、传统机器学习与深度学习的结合、LSTM等算法所需要的数据集来源于矿井传感器测量的粉尘浓度,通过建立回归模型来预测未来矿井的粉尘浓度。传统机器学习算法性能易受数据中极端值的影响。YOLO等计算机视觉算法所需要的数据集来源于…...
unix/linux,sudo,其发展历程详细时间线、由来、历史背景
sudo 的诞生和演化,本身就是一部 Unix/Linux 系统管理哲学变迁的微缩史。来,让我们拨开时间的迷雾,一同探寻 sudo 那波澜壮阔(也颇为实用主义)的发展历程。 历史背景:su的时代与困境 ( 20 世纪 70 年代 - 80 年代初) 在 sudo 出现之前,Unix 系统管理员和需要特权操作的…...
爬虫基础学习day2
# 爬虫设计领域 工商:企查查、天眼查短视频:抖音、快手、西瓜 ---> 飞瓜电商:京东、淘宝、聚美优品、亚马逊 ---> 分析店铺经营决策标题、排名航空:抓取所有航空公司价格 ---> 去哪儿自媒体:采集自媒体数据进…...
分布式增量爬虫实现方案
之前我们在讨论的是分布式爬虫如何实现增量爬取。增量爬虫的目标是只爬取新产生或发生变化的页面,避免重复抓取,以节省资源和时间。 在分布式环境下,增量爬虫的实现需要考虑多个爬虫节点之间的协调和去重。 另一种思路:将增量判…...
大语言模型(LLM)中的KV缓存压缩与动态稀疏注意力机制设计
随着大语言模型(LLM)参数规模的增长,推理阶段的内存占用和计算复杂度成为核心挑战。传统注意力机制的计算复杂度随序列长度呈二次方增长,而KV缓存的内存消耗可能高达数十GB(例如Llama2-7B处理100K token时需50GB内存&a…...
Mobile ALOHA全身模仿学习
一、题目 Mobile ALOHA:通过低成本全身远程操作学习双手移动操作 传统模仿学习(Imitation Learning)缺点:聚焦与桌面操作,缺乏通用任务所需的移动性和灵活性 本论文优点:(1)在ALOHA…...
