C++ 实现对象池的具体方法

目录
  • 前言
  • 一、什么是对象池
  • 二、如何实现
    • 1.确定接口
    • 2.转成代码
  • 三、完整代码
  • 四、使用示例
    • 1、对象复用,示例:
    • 2、简易的线程池,示例:
  • 总结

前言

需求无限,但资源有限的情况下,就需要对资源进行专门的管理。不断的申请和释放内存是不合理的,会造成内存的波动,以及内存不受限的增长。比如,实现了一个消息队列,当发消息的速度快于处理消息的速度时,如果不对资源进行控制,就会导致内存不断的增长。除非有专门的内存管理机制,或明确的编译器优化内存复用,否则建立一个资源管理模块是很有必要的。对象池就是一个对限定数量资源复用管理的模块。

一、什么是对象池

复用对象,消除频繁的对象创建销毁带来的性能消耗,以及避免内存增长的不可控。比如,线程池、连接池都是为了实现复用对象。
举个例子:假设在生产者消费者模型中,生产者生产时创建对象,消费者消费后销毁对象。直接简单的使用new和delete,就会让对象频繁创建和销毁导致额外性能消耗,而且生产者速度大于消费者速度时,就会让对象数量创建大于销毁导致内存不受控制增长。如果使用对象池,就可以让生产和消费复用固定数量的对象,很好的避免了频繁创建销毁对象以及内存增长不受控制的情况。

二、如何实现

1.确定接口

(1)、确定动态关系
通过序列图可以确定对象需要的接口,我们以socket服务为场景绘制序列图,如下

(2)、确定静态关系
根据上面的序列图确定的接口绘制成类图,如下:

2.转成代码

由于模块规模小,接口也不多,所以就不展示进一步细化设计了。因为本文讲述的是C++实现对象池,所以将上述设计转化为C++接口定义。如下:

    /// <summary>
	/// 对象池
	/// </summary>
	class ObjectPool
	{
	public:
		/// <summary>
		/// 构造方法
		/// </summary>
		/// <param name="bufferArray">对象池的缓冲区,由外部指定,可以理解为一个数组。数组大小需满足bufferSize>=elementSize*arraySize</param>
		/// <param name="elementSize">数组元素大小</param>
		/// <param name="arraySize">数组长度或元素个数</param>
		ObjectPool(void* bufferArray, int elementSize, int arraySize );
		/// <summary>
		/// 申请对象
		/// 如果池里对象不足,则会等待,直到有对象才返回。
		/// </summary>
		/// <returns>返回申请的对象指针</returns>
		void* Applicate();
		/// <summary>
		/// 申请对象
		/// </summary>
		/// <param name="timeout">超时时间,超时后返回null</param>
		/// <returns>返回申请的对象指针</returns>
		void* Applicate(int timeout);
		/// <summary>
		/// 归还对象
		/// </summary>
		/// <param name="element">需归还的对象</param>
		void ReturnBack(void* element);
	};

三、完整代码

根据上述的初步设计,再进行细化,以及实现,最终得出如下代码实现。
ObjectPool.h

#ifndef OBJECTPOOL_H
#define OBJECTPOOL_H
/************************************************************************
* @Project:  	AC::ObjectPool
* @Decription:  对象池:“需求很大,但数量有限”的情况下,就需要对资源进行专门的管理,
*不断的申请和释放对象是不合理的(除非有专门的内存管理机制,或明确的编译优化内存复用)。
*这是一个对限定数量资源的复用管理模块。
* @Verision:  	v1.0.0.1
* @Author:  	Xin Nie
* @Create:  	2018/12/21 13:34:00
* @LastUpdate:  2022/1/5 13:53:00
************************************************************************
* Copyright @ 2022. All rights reserved.
************************************************************************/
#include<unordered_map>
#include<vector>
#include<mutex>
#include<condition_variable>
namespace AC {
	/// <summary>
	/// 对象池
	/// </summary>
	class ObjectPool
	{
	public:
		/// <summary>
		/// 构造方法
		/// </summary>
		/// <param name="bufferArray">对象池的缓冲区,由外部指定,可以理解为一个数组。数组大小需满足bufferSize>=elementSize*arraySize</param>
		/// <param name="elementSize">数组元素大小</param>
		/// <param name="arraySize">数组长度或元素个数</param>
		ObjectPool(void* bufferArray, int elementSize, int arraySize );
		/// <summary>
		/// 析构方法
		/// </summary>
		~ObjectPool();
		/// <summary>
		/// 申请对象
		/// 如果池里对象不足,则会等待,直到有对象才返回。
		/// </summary>
		/// <returns>返回申请的对象指针</returns>
		void* Applicate();
		/// <summary>
		/// 申请对象
		/// </summary>
		/// <param name="timeout">超时时间,超时后返回null</param>
		/// <returns>返回申请的对象指针</returns>
		void* Applicate(int timeout);
		/// <summary>
		/// 归还对象
		/// </summary>
		/// <param name="element">需归还的对象</param>
		void ReturnBack(void* element);
		/// <summary>
		/// 获取对象池的缓冲区,即构造方法中的bufferArray
		/// </summary>
		/// <returns>缓冲区的指针</returns>
		void* GetPoolBuffer();
		/// <summary>
		/// 获取对象的大小,即构造方法中的elementSize
		/// </summary>
		/// <returns>对象的大小</returns>
		int GetObjectSize();
		/// <summary>
		/// 获取总的对象数量,即构造方法中的arraySize
		/// </summary>
		/// <returns>总的对象数量</returns>
		int GetObjectCount();
	private:
		void*_buffer = NULL;
		int _elementSize;
		int _arraySize;
		std::vector<void*> _unusedUnits;
		std::unordered_map<void*, int> _usedUnits;
		std::mutex _mutex;
		std::condition_variable _cond;
	};

	/// <summary>
	/// 泛型对象池
	/// </summary>
	/// <typeparam name="T">对象类型</typeparam>
	template<typename T>
	class ObjectPoolGeneric:private ObjectPool
	{
	public:
		/// <summary>
		/// 构造方法
		/// </summary>
		/// <param name="array">对象数组</param>
		/// <param name="size">数组大小</param>
		/// <returns></returns>
		ObjectPoolGeneric(T*array,int size) :ObjectPool(array, sizeof(T), size)
		{
		}
		/// <summary>
		/// 析构方法
		/// </summary>
		~ObjectPoolGeneric() {}
		/// <summary>
		/// 申请对象
		/// 如果池里对象不足,则会等待,直到有对象才返回。
		/// </summary>
		/// <returns>返回申请的对象指针</returns>
		T* Applicate() {
			return (T*)ObjectPool::Applicate();
		}
		/// <summary>
		/// 申请对象
		/// </summary>
		/// <param name="timeout">超时时间,超时后返回null</param>
		/// <returns>返回申请的对象指针</returns>
		T* Applicate(int timeout) {
			return (T*)ObjectPool::Applicate(timeout);
		}
		/// <summary>
		/// 归还对象
		/// </summary>
		/// <param name="element">需归还的对象</param>
		void ReturnBack(T* element)
		{
			ObjectPool::ReturnBack(element);
		}
		/// <summary>
		/// 获取对象池的缓冲区,即构造方法中的bufferArray
		/// </summary>
		/// <returns>缓冲区的指针</returns>
		T* GetPoolBuffer() {
			return (T*)ObjectPool::GetPoolBuffer();
		}
	};
}
#endif

ObjectPool.cpp

#include "ObjectPool.h"
#include <chrono>
namespace AC {
	ObjectPool::ObjectPool(void* bufferArray, int elementSize, int arraySize)
	{
		if (elementSize < 1 || arraySize < 1)
			return;
		_buffer = bufferArray;
		_elementSize = elementSize;
		_arraySize = arraySize;
		char* firstAdress = (char*)bufferArray;
		//记录未使用的索引
		for (int i = 0; i < arraySize; i++)
		{
			_unusedUnits.push_back(&(firstAdress[i * elementSize]));
		}
	}
	ObjectPool::~ObjectPool()
	{
	}
	void* ObjectPool::Applicate()
	{
		return Applicate(-1);
	}
	void* ObjectPool::Applicate(int timeout) {
		void* resource = NULL;
		std::unique_lock<std::mutex> l(_mutex);
		while (_unusedUnits.size() < 1)
		{
			if (timeout == -1)
			{
				_cond.wait(l);
			}
			else if (_cond.wait_for(l, std::chrono::microseconds(timeout)) == std::cv_status::timeout)
			{
				return nullptr;
			}
		}
		resource = _unusedUnits.back();
		_usedUnits[resource] = 1;
		_unusedUnits.pop_back();
		return resource;
	}
	void ObjectPool::ReturnBack(void* element) {
		_mutex.lock();
		auto iter = _usedUnits.find(element);
		if (iter != _usedUnits.end())
		{
			_unusedUnits.push_back(element);
			_usedUnits.erase(iter);
			_cond.notify_one();
		}
		_mutex.unlock();
	}
	void* ObjectPool::GetPoolBuffer()
	{
		return _buffer;
	}
	int ObjectPool::GetObjectSize()
	{
		return _elementSize;
	}
	int ObjectPool::GetObjectCount()
	{
		return _arraySize;
	}
}

四、使用示例

1、对象复用,示例:

#include "ObjectPool.h"
class A {
public:
	A() {
		printf("%p\n", this);
	}
};
int main(int argc, char** argv) {
	//初始化对象池,2个对象
	AC::ObjectPool objectPool(new char[sizeof(A) * 2], sizeof(A), 2);
	A* a, * b, * c;
	//申请对象,使用定位new初始化对象
	a = new (objectPool.Applicate())A;
	b = new (objectPool.Applicate())A;
	//归还对象
	a->~A();//返初始化对象
	objectPool.ReturnBack(a);
	c = new (objectPool.Applicate())A;
	b->~A();
	c->~A();
	//使用结束,删除缓存
	delete	objectPool.GetPoolBuffer();
	return 0;
}

输出:
016502E9
016502E8
016502E9

2、简易的线程池,示例:

#include <thread>
#include <chrono>
#include <mutex>
#include <condition_variable>
#include "ObjectPool.h"
class ThreadInfo {
public:
	std::thread* pThread;
	std::mutex _mutext;
	std::condition_variable _cv;
	std::function<void()> _threadPoc;
};
//线程信息数组,数组长度即线程池的线程数
ThreadInfo _threadArray[3];
//对象池,使用线程信息数组初始化
AC::ObjectPoolGeneric<ThreadInfo>_threadPool(_threadArray, 3);
bool _exitThreadPool = false;
//在线程池中运行方法
void RunInThreadPool(std::function<void()> f) {
	//申请线程
	auto threadInfo = _threadPool.Applicate();
	threadInfo->_threadPoc = f;
	if (threadInfo->pThread)
		//复用线程
	{
		threadInfo->_cv.notify_one();
	}
	else
		//创建线程
	{
		threadInfo->pThread = new std::thread([=]() {
			while (!_exitThreadPool)
			{
				printf("thread %d run\n", threadInfo->pThread->get_id());
				if (threadInfo->_threadPoc)
				{	//执行线程操作
					threadInfo->_threadPoc();
				}
				printf("thread %d stop\n", threadInfo->pThread->get_id());
				//归还线程
				_threadPool.ReturnBack(threadInfo);
				std::unique_lock<std::mutex>lck(threadInfo->_mutext);
				threadInfo->_cv.wait(lck);
			}
		});
	}
}
int main(int argc, char** argv) {
	while(true)
	{   //在线程池中运行方法
		RunInThreadPool([]() {
			std::this_thread::sleep_for(std::chrono::milliseconds(1000));
		});
	}
    return 0;
}

输出:
thread 69664 run
thread 57540 run
thread 56876 run
thread 69664 stop
thread 69664 run
thread 57540 stop
thread 56876 stop
thread 57540 run
thread 56876 run
thread 69664 stop
thread 69664 run
thread 56876 stop
thread 57540 stop
thread 56876 run
thread 57540 run
thread 69664 stop

总结

以上就是今天要讲的内容,本文介绍了对象池的设计与实现以及使用,其使用场景其实不算多,因为很多需要对象复用的场景通常以及有底层实现了,比如线程池数据库的连接池等,所以本文讲的内容只能适用于少数的场景,比如waveOut播放音频时是可以使用对象池实现 的。但总得来说,对象池还是有用的,所以将其写成博客用于记录曾经用过的技术。

到此这篇关于C++ 实现对象池的具体方法的文章就介绍到这了,更多相关C++ 对象池内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 深度剖析C++对象池自动回收技术实现

    对象池可以显著提高性能,如果一个对象的创建非常耗时或非常昂贵,频繁去创建的话会非常低效.对象池通过对象复用的方式来避免重复创建对象,它会事先创建一定数量的对象放到池中,当用户需要创建对象的时候,直接从对象池中获取即可,用完对象之后再放回到对象池中,以便复用.这种方式避免了重复创建耗时或耗资源的大对象,大幅提高了程序性能.本文将探讨对象池的技术特性以及源码实现. 对象池类图 ObjectPool:管理对象实例的pool. Client:使用者. 适用性: 类的实例可重用. 类的实例化过程开销较大.

  • 深入理解C++中的new和delete并实现对象池

    深入理解new和delete new和delete称作运算符 我们转反汇编看看 这2个运算符本质也是相应的运算符的重载的调用 malloc和new的区别? 1.malloc按字节开辟内存的:new开辟内存时需要指定类型 new int[10] 所以malloc开辟内存返回的都是void* 而new相当于运算符的重载函数 operator new ->返回值自动转成指定的类指针 int* 2.malloc只负责开辟空间,new不仅仅有malloc的功能,可以进行数据的初始化 new int(20)

  • C++ 实现对象池的具体方法

    目录 前言 一.什么是对象池 二.如何实现 1.确定接口 2.转成代码 三.完整代码 四.使用示例 1.对象复用,示例: 2.简易的线程池,示例: 总结 前言 需求无限,但资源有限的情况下,就需要对资源进行专门的管理.不断的申请和释放内存是不合理的,会造成内存的波动,以及内存不受限的增长.比如,实现了一个消息队列,当发消息的速度快于处理消息的速度时,如果不对资源进行控制,就会导致内存不断的增长.除非有专门的内存管理机制,或明确的编译器优化内存复用,否则建立一个资源管理模块是很有必要的.对象池就是

  • 如何使用CocosCreator对象池

    前言: 在运行时进行节点的创建( cc.instantiate )和销毁( node.destroy )操作是非常耗费性能的,因此我们在比较复杂的场景中,通常只有在场景初始化逻辑( onLoad )中才会进行节点的创建,在切换场景时才会进行节点的销毁.如果制作有大量敌人或子弹需要反复生成和被消灭的动作类游戏,我们要如何在游戏进行过程中随时创建和销毁节点呢?这里就需要对象池的帮助了. 对象池就是一组可回收的节点对象,我们通过创建cc.NodePool的实例来初始化一种节点的对象池.通常当我们有多个

  • .NET Core对象池的应用:扩展篇

    目录 一.池化集合 二.池化StringBuilder 三.ArrayPool<T> 四.MemoryPool<T> 原则上所有的引用类型对象都可以通过对象池来提供,但是在具体的应用中需要权衡是否值得用.虽然对象池能够通过对象复用的方式避免GC,但是它存储的对象会耗用内存,如果对象复用的频率很小,使用对象池是不值的.如果某个小对象的使用周期很短,能够确保GC在第0代就能将其回收,这样的对象其实也不太适合放在对象池中,因为第0代GC的性能其实是很高的.除此之外,对象释放到对象池之后就

  • .NET Core对象池的应用:设计篇

    目录 一. IPooledObjectPolicy<T> 二.ObjectPool<T> DefaultObjectPool<T> DisposableObjectPool<T> 三.ObjectPoolProvider <编程篇>已经涉及到了对象池模型的大部分核心接口和类型.对象池模型其实是很简单的,不过其中有一些为了提升性能而刻意为之的实现细节倒是值得我们关注.总的来说,对象池模型由三个核心对象构成,它们分别是表示对象池的ObjectPool

  • tomcat中Servlet对象池介绍及如何使用

    tomcat中Servlet对象池 Servlet在不实现SingleThreadModel的情况下运行时是以单个实例模式,如下图,这种情况下,Wrapper容器只会通过反射实例化一个Servlet对象,对应此Servlet的所有客户端请求都会共用此Servlet对象,而对于多个客户端请求tomcat会使用多线程处理,所以应该保证此Servlet对象的线程安全,多个线程不管执行顺序如何都能保证执行结果的正确性.例如刚做web应用开发时可能会犯的一个错误:在某个Servlet中使用成员变量累加去统

  • 建一个XMLHttpRequest对象池

    作者:legend 出处:http://www.ugia.cn/?p=85 在ajax应用中,通常一个页面要同时发送多个请求,如果只有一个XMLHttpRequest对象,前面的请求还未完成,后面的就会把前面的覆盖掉,如果每次都创建一个新的XMLHttpRequest对象,也会造成浪费.解决的办法就是创建一个XMLHttpRequset的对象池,如果池里有空闲的对象,则使用此对象,否则将创建一个新的对象.下面是我最近写的一个简单的类: 复制代码 代码如下: /** * XMLHttpReques

  • Python设计模式编程中的备忘录模式与对象池模式示例

    Memento备忘录模式 备忘录模式一个最好想象的例子:undo! 它对对象的一个状态进行了'快照', 在你需要的时候恢复原貌.做前端会有一个场景:你设计一个表单,当点击提交会对表单内容 验证,这个时候你就要对用户填写的数据复制下来,当用户填写的不正确或者格式不对等问题, 就可以使用快照数据恢复用户已经填好的,而不是让用户重新来一遍,不是嘛? python的例子 这里实现了一个事务提交的例子 import copy def Memento(obj, deep=False): # 对你要做快照的对

  • 举例讲解Java设计模式中的对象池模式编程

    定义 一个对象池是一组已经初始化过且可以使用的对象的集合,池的用户可以从池子中取得对象,对其进行操作处理,并在不需要时归还给池子而非销毁它. 若初始化.实例化的代价高,且有需求需要经常实例化,但每次实例化的数量较少的情况下,使用对象池可以获得显著的效能提升.从池子中取得对象的时间是可预测的,但新建一个实例所需的时间是不确定. 实现 1. Reusable - 对象池中的对象,通常实例化代价比较高. 2. Client - 使用一个对象的实例. 3. ReusablePool - 管理对象的实例化

  • java客户端Jedis操作Redis Sentinel 连接池的实现方法

    pom.xml配置 <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId> <version>1.0.2.RELEASE</version> </dependency> <dependency> <groupId>redis.clients<

  • Spring Boot中配置定时任务、线程池与多线程池执行的方法

    配置基础的定时任务 最基本的配置方法,而且这样配置定时任务是单线程串行执行的,也就是说每次只能有一个定时任务可以执行,可以试着声明两个方法,在方法内写一个死循环,会发现一直卡在一个任务上不动,另一个也没有执行. 1.启动类 添加@EnableScheduling开启对定时任务的支持 @EnableScheduling @SpringBootApplication public class TestScheduledApplication extends SpringBootServletInit

随机推荐