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

news2024/12/26 22:16:09

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 pool

import (
	"errors"
	"io"
	"log"
	"sync"
)

// 说明go 1.6之后,自带sync.Pool用来管理资源

// Pool 管理用户资源的池子,可以在多个goroutine之间共享资源
type Pool struct {
	m         sync.Mutex
	resources chan io.Closer
	factory   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 main

import (
	"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.WaitGroup
	wg.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.coloradmin.cn/o/2252227.html

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

相关文章

马铃薯病害识别(VGG-16复现)

VGG16-Pytorch实现马铃薯病害识别 &#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客 &#x1f356; 原作者&#xff1a;K同学啊 电脑系统&#xff1a;Windows11 显卡型号&#xff1a;NVIDIA Quadro P620 语言环境&#xff1a;python 3.9.7 编译器&am…

HCSIF: 中国区域2000-2022年高时空分辨率(500m)SIF数据集

日光诱导叶绿素荧光&#xff08;Solar-induced chlorophyll fluorescence, SIF&#xff09;被誉为“植被光合作用的探针”。2017年&#xff0c;搭载在Sentinel-5P卫星上的 TROPOMI (TROPOspheric Monitoring Instrument&#xff09;传感器成功发射&#xff0c;该卫星同时具有高…

STL:相同Size大小的vector和list哪个占用空间多?

在C中&#xff0c;vector和list是两种不同的序列容器。vector底层是连续的内存&#xff0c;而list是非连续的&#xff0c;分散存储的。因此&#xff0c;vector占用的空间更多&#xff0c;因为它需要为存储的元素分配连续的内存空间。 具体占用多少空间&#xff0c;取决于它们分…

蓝牙设备驱动开发

文章目录 一、蓝牙协议架构二、蓝牙协议的HCI传输层三、编程框架 一、蓝牙协议架构 蓝牙是无线数据和语音传输的开放式标准&#xff0c;它将各种通信设备、计算机及其终端设备、各种数字数据系统、甚至家用电器采用无线方式联接起来。它的传输距离为10cm&#xff5e;10m&#…

【计算机网络】实验7:默认路由和特定主机路由以及路由环路问题

实验 7&#xff1a;默认路由和特定主机路由以及路由环路问题 一、 实验目的 了解默认路由以及特定主机路由。 了解静态路由配置错误导致的路由环路问题。 二、 实验环境 • Cisco Packet Tracer 模拟器 三、 实验内容 1、默认路由以及特定主机路由 (1) 第一步&#xff…

安装 RabbitMQ 服务

安装 RabbitMQ 服务 一. RabbitMQ 需要依赖 Erlang/OTP 环境 (1) 先去 RabbitMQ 官网&#xff0c;查看 RabbitMQ 需要的 Erlang 支持&#xff1a;https://www.rabbitmq.com/ 进入官网&#xff0c;在 Docs -> Install and Upgrade -> Erlang Version Requirements (2) …

MiniProfiler WebAPI 分析工具

一、介绍&#x1f6e0;️ MiniProfiler 是一款简单但有效的 .NET、Ruby、Go 和 Node.js 微型 性能分析器 。 MiniProfiler 不会将自身附加到每个方法调用;那会太具有侵入性&#xff0c;并且不会专注于最大的性能问题。相反&#xff0c;它提供&#xff1a; &#x1f538;ADO.…

Java个人博客系统项目文档

项目名称 Java个人博客系统 项目概述 该博客系统是一个多功能的Java应用程序。该系统支持用户发布新文章、浏览他人文章、管理个人文章收藏和删除不再需要的文章。通过该博客系统&#xff0c;用户可以享受一个安全、便捷的在线写作和阅读体验。 运行环境 编程语言&#xff1…

华为HarmonyOS 让应用快速拥有账号能力 - 获取用户头像昵称

场景介绍 如应用需要完善用户头像昵称信息&#xff0c;可使用Account Kit提供的头像昵称授权能力&#xff0c;用户允许应用获取头像昵称后&#xff0c;可快速完成个人信息填写。以下只针对Account kit提供的头像昵称授权能力进行介绍&#xff0c;若要获取头像还可通过场景化控…

Hadoop生态圈框架部署 伪集群版(一)- Linux操作系统安装及配置

文章目录 前言一、下载CentOS镜像1. 下载 二、创建虚拟机hadoop三、CentOS安装与配置1. 安装CentOS2. 配置虚拟网络及虚拟网卡2.1 配置虚拟网络2.2 配置虚拟网卡 3. 安装 SSH 远程连接工具 FinalShell3.1 简介3.2 下载和安装3.2.1 下载3.2.2 安装 3.3 查看动态ip地址3.4 使用Fi…

StarRocks存算分离在得物的降本增效实践

一、背景 OLAP引擎在得物的客服、风控、供应链、投放、运营、ab实验等大量业务场景发挥重要作用&#xff0c;在报表、日志、实时数仓等应用场景都有广泛的应用。 得物引入和使用OLAP引擎的过程中&#xff0c;每个业务都基于自己的需求选择当时最适合自己的引擎。现在得物内部同…

L15.【LeetCode笔记】相同的树

目录 1.题目 代码模板 2.分析 通过合理的if判断分类讨论两个根节点 1.首先,p和q都为NULL的情况最好排除 2.排除了两个都为NULL的情况,剩下的情况:1.其中一个为NULL;2.两个都不为NULL 写法1 写法2 3.只剩下最后一种情况:p和q都不为NULL 3.代码 提交结果 1.题目 https…

TCP协议(一)

TCP协议&#xff08;一&#xff09; 一、TCP协议1、介绍2、主要特点 二、TCP协议段格式1、示意图2、介绍 三、确认应答机制1、基本流程2、作用3、示意图4、序列号示意图 四、超时重传机制1、介绍2、重传超时时间3、往返时间4、平滑往返时间5、系统实现6、数据丢失重传示意图7、…

单链表---链表分割

将小于x的结点放在前面&#xff0c;大于等于x的结点放在后面&#xff0c;不改变结点相对位置&#xff0c;输出更改后的链表首结点。 typedef struct ListNode {ListNode* next;int val; }ListNode; 思路&#xff1a;我们可以新创建两个链表指针&#xff0c;将小于x的结点全部…

Android V reboot重启后电量从0直接跳到100%

问题背景 设备每次重启之后statusBar的电量百分比一开始是0,然后过了2s左右的样子电量才恢复为100% 解决方案 看拿到的串口日志来看,重启之后 healthd 拿到的电量值都是100%,而且这一块的电量也没有变化,重启前后电量都是100%: 自己尝试复现,在Android 15上测试重启,…

【技巧】Mac上如何显示键盘和鼠标操作

在制作视频教程时&#xff0c;将键盘和鼠标的操作在屏幕上显示出来&#xff0c;会帮助观众更容易地理解。 推荐Mac上两款开源的小软件。 1. KeyCastr 这款工具从2009年至今一直在更新中。 https://github.com/keycastr/keycastr 安装的话&#xff0c;可以从Github上下载最…

C 语言学习的经典书籍有哪些?

学习C语言的理由 C语言是一种程席设计语言&#xff0c;它是由美国AT&T公司贝尔实验室的Dennis Ritchie于1972年发明的。C语言之所以流行&#xff0c;是因为它简单易用。学习C语言的几个理由如下&#xff1a; (1)C、C#和Java使用一种被称为面向对象程序设计(0bject-Orient…

leetcode 52. N 皇后 II 困难

n 皇后问题 研究的是如何将 n 个皇后放置在 n n 的棋盘上&#xff0c;并且使皇后彼此之间不能相互攻击。 给你一个整数 n &#xff0c;返回 n 皇后问题 不同的解决方案的数量。 示例 1&#xff1a; 输入&#xff1a;n 4 输出&#xff1a;2 解释&#xff1a;如上图所示&#…

谷歌浏览器中搜索引擎的设置与管理

谷歌浏览器作为全球最受欢迎的网络浏览器之一&#xff0c;以其高速、稳定和丰富的功能深受用户喜爱。对于许多用户来说&#xff0c;自定义和管理搜索引擎是提升浏览体验的重要一环。本文将详细介绍如何在谷歌浏览器中设置和管理搜索引擎&#xff0c;包括如何修改默认搜索引擎、…

掌上单片机实验室 — RT - Thread+ROS2 浅尝(26)

前面化解了Micro_ROS通讯问题&#xff0c;并在 RT-Thread Studio 环境下&#xff0c;使用Micro_ROS软件包中的例程&#xff0c;实现了STM32F411CE核心板和ROS2主机的通讯。之后还尝试修改例程 micro_ros_sub_twist.c &#xff0c;实现了接收 turtle_teleop_key 所发出的 turtle…