C++ 實現對象池的具體方法

前言

需求無限,但資源有限的情況下,就需要對資源進行專門的管理。不斷的申請和釋放內存是不合理的,會造成內存的波動,以及內存不受限的增長。比如,實現瞭一個消息隊列,當發消息的速度快於處理消息的速度時,如果不對資源進行控制,就會導致內存不斷的增長。除非有專門的內存管理機制,或明確的編譯器優化內存復用,否則建立一個資源管理模塊是很有必要的。對象池就是一個對限定數量資源復用管理的模塊。

一、什麼是對象池

復用對象,消除頻繁的對象創建銷毀帶來的性能消耗,以及避免內存增長的不可控。比如,線程池、連接池都是為瞭實現復用對象。
舉個例子:假設在生產者消費者模型中,生產者生產時創建對象,消費者消費後銷毀對象。直接簡單的使用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++ 對象池內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: