对象池模式

基础

对象池模式(Object Pool Pattern)是一种设计模式,旨在管理对象的重用,以减少频繁创建和销毁对象的开销。在需要创建和销毁大量相似对象时,使用对象池可以提高性能,尤其是在性能敏感的场合,比如游戏开发或高频交易系统。

对象池模式的基本概念

  1. 池(Pool):存储可复用对象的集合。
  2. 借用(Borrowing):从池中获取一个对象。
  3. 归还(Returning):使用完后将对象放回池中,以便重用。

使用现代 C++ 实现对象池

第一步:定义对象

我们先定义一个简单的对象类 Object

#include <iostream>class Object {
public:Object() {std::cout << "Object Created" << std::endl;}~Object() {std::cout << "Object Destroyed" << std::endl;}void doSomething() {std::cout << "Doing something with object!" << std::endl;}
};
第二步:实现对象池

接下来,我们实现一个对象池类 ObjectPool

#include <vector>
#include <memory>
#include <iostream>class ObjectPool {
public:ObjectPool(size_t initialSize) {for (size_t i = 0; i < initialSize; ++i) {pool.push_back(std::make_unique<Object>());}}std::unique_ptr<Object> borrowObject() {if (pool.empty()) {std::cout << "No available objects, creating a new one." << std::endl;return std::make_unique<Object>();} else {auto obj = std::move(pool.back());pool.pop_back();return obj;}}void returnObject(std::unique_ptr<Object> obj) {pool.push_back(std::move(obj));}private:std::vector<std::unique_ptr<Object>> pool;
};
第三步:使用对象池

现在,我们可以在 main 函数中使用对象池。

int main() {ObjectPool pool(3); // 初始化对象池,预先创建3个对象// 借用对象auto obj1 = pool.borrowObject();obj1->doSomething();// 归还对象pool.returnObject(std::move(obj1));// 再次借用对象auto obj2 = pool.borrowObject();obj2->doSomething();// 归还对象pool.returnObject(std::move(obj2));// 借用更多对象auto obj3 = pool.borrowObject();auto obj4 = pool.borrowObject();auto obj5 = pool.borrowObject();auto obj6 = pool.borrowObject(); // 这里会创建一个新对象return 0;
}

对象池模式特别适合于那些频繁创建和销毁对象的场景。

More example

模拟一个数据库连接池,用于管理多个数据库连接。这样,我们可以避免频繁地打开和关闭连接,从而提升系统的效率。

复杂例子:数据库连接池

第一步:定义数据库连接类

我们定义一个模拟的 DatabaseConnection 类,用于表示一个数据库连接。

#include <iostream>
#include <string>
#include <thread>
#include <chrono>class DatabaseConnection {
public:// 构造函数:接受连接字符串并连接数据库DatabaseConnection(const std::string& connectionStr) : connectionStr_(connectionStr), connected_(false) {connect();}// 析构函数:断开连接~DatabaseConnection() {disconnect();}// 模拟连接数据库的过程void connect() {if (!connected_) {std::cout << "Connecting to database: " << connectionStr_ << std::endl;std::this_thread::sleep_for(std::chrono::milliseconds(500)); // 模拟连接延迟connected_ = true;}}// 断开数据库连接void disconnect() {if (connected_) {std::cout << "Disconnecting from database: " << connectionStr_ << std::endl;connected_ = false;}}// 模拟执行数据库查询void query(const std::string& sql) {if (connected_) {std::cout << "Executing query: " << sql << " on " << connectionStr_ << std::endl;} else {std::cerr << "Database not connected!" << std::endl;}}private:std::string connectionStr_;  // 数据库连接字符串bool connected_;             // 连接状态标志
};
第二步:实现数据库连接池

创建一个 DatabaseConnectionPool 类,使用对象池管理数据库连接。这里使用 std::shared_ptr 来实现连接的共享和重用。

#include <vector>
#include <memory>
#include <mutex>
#include <string>
#include <queue>
#include <condition_variable>class DatabaseConnectionPool {
public:// 构造函数:初始化连接池并创建一定数量的数据库连接DatabaseConnectionPool(const std::string& connectionStr, size_t poolSize): connectionStr_(connectionStr), poolSize_(poolSize) {// 创建指定数量的连接并存入池中for (size_t i = 0; i < poolSize_; ++i) {pool_.push(std::make_shared<DatabaseConnection>(connectionStr_));}}// 获取一个连接,如果池中没有可用连接则阻塞等待std::shared_ptr<DatabaseConnection> acquireConnection() {//互斥锁确保多线程访问时的线程安全std::unique_lock<std::mutex> lock(mutex_);// 条件变量等待,若池中无可用连接,阻塞当前线程并等待直到有连接归还到池中。cond_.wait(lock, [this]() { return !pool_.empty(); });// 从池中取出一个连接auto connection = pool_.front();pool_.pop();// 使用自定义删除器,将连接自动归还到池中return std::shared_ptr<DatabaseConnection>(connection.get(), [this](DatabaseConnection* conn) { std::unique_lock<std::mutex> lock(mutex_);// 将连接归还到池中pool_.push(std::shared_ptr<DatabaseConnection>(conn));// 通知其他等待线程有新的连接可用cond_.notify_one(); });}private:std::string connectionStr_;                       // 数据库连接字符串size_t poolSize_;                                 // 池的大小(预先创建的连接数)std::queue<std::shared_ptr<DatabaseConnection>> pool_; // 存储连接的队列std::mutex mutex_;                                // 用于线程安全的互斥锁std::condition_variable cond_;                    // 条件变量,用于管理等待的线程
};

acquireConnection 方法中,使用了自定义的删除器,这样当 std::shared_ptr 的引用计数减少到零时,连接会自动归还到池中。

第三步:使用数据库连接池
int main() {// 创建一个数据库连接池,预先创建 3 个数据库连接DatabaseConnectionPool dbPool("Server=127.0.0.1;Database=testdb;", 3);{// 从连接池中借用一个连接auto conn1 = dbPool.acquireConnection();conn1->query("SELECT * FROM users"); // 使用连接执行查询// 借用第二个连接auto conn2 = dbPool.acquireConnection();conn2->query("SELECT * FROM orders"); // 使用连接执行查询// 借用第三个连接auto conn3 = dbPool.acquireConnection();conn3->query("SELECT * FROM products"); // 使用连接执行查询// conn1, conn2, conn3 都在作用域内,因此此时池中没有可用连接// 如果再请求一个连接,当前线程会阻塞,直到有连接被归还} // 离开作用域,conn1, conn2, conn3 自动销毁并归还到连接池中// 作用域结束后,池中有连接可以使用,再次借用连接auto conn4 = dbPool.acquireConnection();conn4->query("SELECT * FROM logs"); // 使用连接执行查询return 0;
}
运行流程
  1. 创建数据库连接池 dbPool,并初始化 3 个连接。
  2. acquireConnection 会从池中取出一个连接;如果没有连接,线程会阻塞等待。
  3. 使用完连接后,智能指针销毁时触发自定义删除器,将连接归还到池中,避免显式地 release

注意点

  1. 线程安全:对象池常常用于多线程环境,需要用互斥锁(std::mutex)和条件变量(std::condition_variable)来确保线程安全。
  2. 连接超时:在实际项目中,可以为 acquireConnection 增加超时参数,如可以使用 condition_variable::wait_for 设置超时时间,如果等待时间超过设定值,就返回 nullptr,避免线程长时间阻塞。
  3. 最大池容量:可以动态调整池的大小或设定池的最大容量,当需求量大于容量时,临时创建新对象,而不是等待。

拓展点

  1. 惰性初始化:在对象池初始化时不创建对象,而是根据请求动态创建,可以减少启动时的资源占用。
  2. 双层对象池:为频繁使用的对象(如小对象和大对象)分别创建对象池,从而提高内存和性能管理的精细度。
  3. 调度策略:可以引入优先级调度,例如优先分配最近未使用的对象,或根据对象的状态和属性进行选择。

总结

对象池模式在现代 C++ 中结合智能指针、条件变量、互斥锁等技术,可以实现高效、易用的资源管理器。在实际应用中,数据库连接池只是一个简单的示例,对象池还可以应用于线程池、网络连接池、文件句柄池等各种场景。

与线程池的关系

对象池和线程池在设计模式上非常相似,它们都通过复用有限资源来减少创建和销毁资源的开销。这两者的核心思想相同:使用池管理资源,避免频繁的分配和释放,但它们应用的场景和管理的资源有所不同。

对象池与线程池的相似之处

  1. 资源复用:无论是对象池还是线程池,都是为了管理有限的资源池,避免频繁的资源分配和回收。
  2. 管理资源的生命周期:两者都通过池的机制来统一管理资源的创建、借用、归还、销毁等生命周期,避免内存泄漏或资源浪费。
  3. 多线程安全:在多线程环境中,两者都需要确保资源的安全访问,一般通过互斥锁(mutex)和条件变量(condition_variable)来管理资源的获取与归还,避免资源竞争。
  4. 借用和归还机制:两者都通过“借用”和“归还”的方式来管理资源的使用。资源借出后可以在外部使用,用完后再放回池中,以便再次被其他任务使用。

对象池与线程池的不同之处

虽然它们在设计模式上相似,但用途和实现细节有所不同:

  1. 资源类型

    • 对象池:管理的是一般的对象实例(如数据库连接、文件句柄等),这些对象一般代表一个外部资源或具有较高创建/销毁开销的对象。
    • 线程池:管理的是线程或任务执行的资源,主要用于控制线程的数量,并让任务在已有的线程中并行执行,从而避免线程频繁创建和销毁的开销。
  2. 资源的使用方式

    • 对象池:对象可以是任何类型的数据结构或连接,不仅限于线程。用户借用对象时一般直接调用对象的方法。
    • 线程池:线程池内部封装了线程管理逻辑,用户提交任务后,线程池会从任务队列中提取任务并分配给线程执行,用户不直接控制线程对象。
  3. 任务调度

    • 对象池:对象池通常不会处理任务调度,而是将对象直接提供给用户使用。
    • 线程池:线程池具有任务调度的职责,用户向线程池提交任务后,线程池负责管理任务队列、分配任务给空闲线程并调度任务的执行。

示例:简化的线程池实现(对比对象池)

为了更直观地展示线程池和对象池的不同,这里给出一个简单的线程池实现。这个线程池将使用一个任务队列来管理用户提交的任务,并由线程池中的线程从队列中取出任务并执行。

简化的线程池实现代码
#include <iostream>
#include <thread>
#include <vector>
#include <queue>
#include <functional>
#include <mutex>
#include <condition_variable>
#include <future>
#include <atomic>class ThreadPool {
public:ThreadPool(size_t poolSize) : stop(false) {// 创建指定数量的线程for (size_t i = 0; i < poolSize; ++i) {workers.emplace_back([this]() {while (true) {std::function<void()> task;{std::unique_lock<std::mutex> lock(this->queueMutex);// 等待任务,或线程池停止this->condition.wait(lock, [this]() { return this->stop || !this->tasks.empty(); });// 如果线程池停止且任务队列为空,则退出if (this->stop && this->tasks.empty()) {return;}// 从任务队列中取出一个任务task = std::move(this->tasks.front());this->tasks.pop();}// 执行任务task();}});}}// 添加任务到任务队列template <class F, class... Args>auto enqueue(F&& f, Args&&... args) -> std::future<typename std::result_of<F(Args...)>::type> {using return_type = typename std::result_of<F(Args...)>::type;// 打包任务,使其可以存储到队列中auto task = std::make_shared<std::packaged_task<return_type()>>(std::bind(std::forward<F>(f), std::forward<Args>(args)...));std::future<return_type> res = task->get_future();{std::unique_lock<std::mutex> lock(queueMutex);// 停止状态下不允许添加新任务if (stop) {throw std::runtime_error("enqueue on stopped ThreadPool");}// 将任务添加到队列tasks.emplace([task]() { (*task)(); });}// 通知一个等待中的线程去处理任务condition.notify_one();return res;}// 停止线程池~ThreadPool() {{std::unique_lock<std::mutex> lock(queueMutex);stop = true;}// 通知所有线程退出condition.notify_all();// 等待所有线程完成任务并退出for (std::thread &worker : workers) {worker.join();}}private:std::vector<std::thread> workers;                // 工作线程集合std::queue<std::function<void()>> tasks;         // 任务队列std::mutex queueMutex;                           // 任务队列的互斥锁std::condition_variable condition;               // 条件变量,通知线程有任务可执行std::atomic<bool> stop;                          // 停止线程池标志
};
使用线程池
int main() {ThreadPool pool(4); // 创建线程池,包含4个线程// 向线程池提交任务,并获取任务的 future 以便同步获取结果auto result1 = pool.enqueue([](int x) { std::this_thread::sleep_for(std::chrono::seconds(1));return x * x; }, 5);auto result2 = pool.enqueue([]() { std::cout << "Task 2 is running" << std::endl; });std::cout << "Result of Task 1: " << result1.get() << std::endl; // 阻塞等待结果result2.get(); // 确保任务2完成return 0;
}

总结

在这个线程池实现中:

  • 任务调度:线程池维护一个任务队列并负责调度任务的执行。
  • 线程管理:线程池管理了固定数量的线程来执行任务,避免频繁创建和销毁线程。
  • 资源复用:线程池中的线程会反复执行多个任务,而不是每个任务都创建新线程。

通过对比可以看出,对象池管理的是一些资源的生命周期,而线程池则管理任务的调度和线程的生命周期。线程池在任务密集型的程序中非常有用,而对象池则适合于高成本、可重用的资源管理。两者都在现代编程中扮演着重要角色,用于提升系统的性能和资源利用效率。

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

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

相关文章

energy 发布 v2.4.5

更新内容 修复 energy cli install 命令安装开发环境 修复 动态库加载error未暴露 增加 JS ipc.on 监听模式&#xff0c;异步返回结果 修复 energy cli 不能强制退出问题 修复 MacOS 开发模式 debug 时不更新 helper 进程 优化 energy cli 在 MacOS 开发模式和安装包制作 link…

LeetCode 19. 删除链表的倒数第 N 个结点(java)

目录 题目描述: 代码: 第一种: 第二种: 题目描述: 给你一个链表&#xff0c;删除链表的倒数第 n 个结点&#xff0c;并且返回链表的头结点。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5], n 2 输出&#xff1a;[1,2,3,5]示例 2&#xff1a; 输入&#xff1a;h…

IMU应用于监测进食

最近&#xff0c;日本研究团队成功研发了一种创新的进食速度监测系统&#xff0c;巧妙融合IMU技术&#xff0c;旨在深入研究并有效评估个体在自由生活环境下的进食习惯。 实验中&#xff0c;科研团队把IMU传感器固定在受试者佩戴的腕带中&#xff0c;以监测并记录进食手腕时的运…

WSL开发--利用Git连接远程仓库(详细步骤)

这篇文章主要介绍了如何将本地项目推送到 GitLab 上&#xff0c;并且避免每次提交都需要输入用户名和密码。文中分步讲解了配置 GitLab SSH 密钥以及配置 Git 远程仓库地址的方法。以下是文章的优化和简洁版&#xff1a; 将本地项目推送到 GitLab 并配置 SSH 免密登录 为了方便…

LeetCode100之盛最多水的容器(11)--Java

1.问题描述 给定一个长度为 n 的整数数组 height 。有 n 条垂线&#xff0c;第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。找出其中的两条线&#xff0c;使得它们与 x 轴共同构成的容器可以容纳最多的水。返回容器可以储存的最大水量 注意 你不能倾斜容器 示例1 输入&…

算法实现 - 快速排序(Quick Sort) - 理解版

文章目录 算法介绍算法分析核心思想三个版本运行过程挖坑法Hoare 原版前后指针法 算法稳定性和复杂度稳定性时间复杂度平均情况O(nlogn)最差情况O( n 2 n^2 n2) 空间复杂度 算法介绍 快速排序是一种高效的排序算法&#xff0c;由英国计算机科学家C. A. R. Hoare在1960年提出&a…

设备搜索相关协议使用

一、实现原理 首先&#xff0c;Client -> Gateway : 发送 UDP 广播包&#xff08;含厂商自定义协议)这一步表示客户端开始向网络中发送一个包含厂商自定义协议的 UDP 广播包&#xff0c;目的是寻找本厂商的设备&#xff08;网关&#xff09;。客户端此时处于活动状态activa…

视频去水印怎么办?两种方法教会你

视频有水印的话确实很恼火&#xff0c;想要干净的去除视频水印&#xff0c;这里分享两种简单又实用的方法。 方法一&#xff1a;美图秀秀 大家都熟悉的修图神器&#xff0c;功能超全。不仅能把照片P得美美哒&#xff0c;还能去掉照片和视频上的水印呢&#xff01;用起来挺顺手…

【案例】旗帜飘动

开发平台&#xff1a;Unity 6.0 开发工具&#xff1a;Shader Graph 参考视频&#xff1a;Unity Shader Graph 旗帜飘动特效   一、效果图 二、Shader Graph 路线图 三、案例分析 核心思路&#xff1a;顶点偏移计算 与 顶点偏移忽略 3.1 纹理偏移 视觉上让旗帜保持动态飘动&a…

PHP合成图片,生成海报图,poster-editor使用说明

之前写过一篇使用Grafika插件生成海报图的文章&#xff0c;但是当我再次使用时&#xff0c;却发生了错误&#xff0c;回看Grafika文档&#xff0c;发现很久没更新了&#xff0c;不兼容新版的GD&#xff0c;所以改用了intervention/image插件来生成海报图。 但是后来需要对海报…

智慧汇聚:十款企业培训工具打造学习型企业

在当今快速变化的商业环境中&#xff0c;企业要想保持竞争力&#xff0c;就必须不断适应新技术、新市场和新的工作方式。构建一个学习型企业&#xff0c;不仅能够促进员工的个人成长&#xff0c;还能增强团队的整体能力和企业的创新能力。为了实现这一目标&#xff0c;借助先进…

「C/C++」C/C++标准库 之 #include<cstdlib> 通用工具函数库

✨博客主页何曾参静谧的博客&#x1f4cc;文章专栏「C/C」C/C程序设计&#x1f4da;全部专栏「VS」Visual Studio「C/C」C/C程序设计「UG/NX」BlockUI集合「Win」Windows程序设计「DSA」数据结构与算法「UG/NX」NX二次开发「QT」QT5程序设计「File」数据文件格式「PK」Parasoli…

pycharm调用方法时显示为灰色

不用担心&#xff0c;亮的证明是被用过得&#xff0c;灰色的没有被用&#xff0c;引用一下就会变正常了

机器学习:我们能用机器学习来建立投资模型吗

机器学习模型能解决什么投资问题&#xff1f; 利用机器学习解决投资问题的思路&#xff0c;其实和在互联网领域解决推荐、广告问题的思路是一样的&#xff0c;只不过利用的特征完全变了。推荐、广告模型利用的是用户的年龄、性别&#xff0c;物品的类别、价格等特征&#xff0c…

ClkLog企业版(CDP)预售开启,更有鸿蒙SDK前来助力

新版本发布 ClkLog在上线近1年后&#xff0c;获得了客户的一致肯定与好评&#xff0c;并收到了不少客户对功能需求的反馈。根据客户的反馈&#xff0c;我们在今年三季度对ClkLog的版本进行了重新的规划与调整&#xff0c;简化了原有的版本类型&#xff0c;方便客户进行选择。 与…

【Java设计模式】16-26章

行为型模式&#xff1a; 第16章 12模板方法模式(Template) 16.1 豆浆制作问题 16.2 模板方法模式基本介绍 16.3 模板方法模式原理类图 16.4 模板方法模式解决豆浆制作问题 package com.atguigu.template;//抽象类&#xff0c;表示豆浆 public abstract class SoyaMilk {//模板…

上市公司环境信息披露质量评分数据王婉菁版(2008-2023年)噪声光污染辐射废水减排等治理

数据来源基础数据来源于上市公司年报、社会责任报告、环境报告以及证监会和统计局。时间跨度2008-2023区域跨度企业及行业层面 核心解释变量&#xff1a;环境信息披露质量&#xff0c;是由上市公司环境管理、负债、业绩于治理、披露载体等各项信息披露质量评分加总。 被解释变…

Llama 3.2 Vision Molmo:多模态开源生态系统基础

编者按&#xff1a; 视觉功能的融入对模型能力和推理方式的影响如何&#xff1f;当我们需要一个既能看懂图像、又能生成文本的 AI 助手时&#xff0c;是否只能依赖于 GPT-4V 这样的闭源解决方案&#xff1f; 我们今天为大家分享的这篇文章&#xff0c;作者的核心观点是&#xf…

el-table 滚动条重置 手动控制滚动条

最近在使用 el-table 的时候&#xff0c;出现一个问题&#xff1a; 表头过长的时候&#xff0c;会有左右滑动的操作&#xff0c;当我们把表格拉到最右侧&#xff0c;这个时候重新请求数据的话&#xff0c;表格位置还是在最右侧&#xff0c;不会恢复原位。 那我们想恢复原位&a…

Windows的MySQL开机自动启动问题

标题 问题描述 问题描述 在Windows系统中&#xff0c;我设置好了MySQL服务为自动启动&#xff0c;但在开机后发现MySQL服务任没有自动运行。我有点苦恼&#xff0c;每次连接MySQL&#xff0c;都要进入计算机管理&#xff0c;手动打开。 解决方法&#xff1a; 1.前提安装好MySQ…