C++ 實(shí)現(xiàn)對(duì)象池的具體方法
前言
需求無(wú)限,但資源有限的情況下,就需要對(duì)資源進(jìn)行專門(mén)的管理。不斷的申請(qǐng)和釋放內(nèi)存是不合理的,會(huì)造成內(nèi)存的波動(dòng),以及內(nèi)存不受限的增長(zhǎng)。比如,實(shí)現(xiàn)了一個(gè)消息隊(duì)列,當(dāng)發(fā)消息的速度快于處理消息的速度時(shí),如果不對(duì)資源進(jìn)行控制,就會(huì)導(dǎo)致內(nèi)存不斷的增長(zhǎng)。除非有專門(mén)的內(nèi)存管理機(jī)制,或明確的編譯器優(yōu)化內(nèi)存復(fù)用,否則建立一個(gè)資源管理模塊是很有必要的。對(duì)象池就是一個(gè)對(duì)限定數(shù)量資源復(fù)用管理的模塊。
一、什么是對(duì)象池
復(fù)用對(duì)象,消除頻繁的對(duì)象創(chuàng)建銷毀帶來(lái)的性能消耗,以及避免內(nèi)存增長(zhǎng)的不可控。比如,線程池、連接池都是為了實(shí)現(xiàn)復(fù)用對(duì)象。
舉個(gè)例子:假設(shè)在生產(chǎn)者消費(fèi)者模型中,生產(chǎn)者生產(chǎn)時(shí)創(chuàng)建對(duì)象,消費(fèi)者消費(fèi)后銷毀對(duì)象。直接簡(jiǎn)單的使用new和delete,就會(huì)讓對(duì)象頻繁創(chuàng)建和銷毀導(dǎo)致額外性能消耗,而且生產(chǎn)者速度大于消費(fèi)者速度時(shí),就會(huì)讓對(duì)象數(shù)量創(chuàng)建大于銷毀導(dǎo)致內(nèi)存不受控制增長(zhǎng)。如果使用對(duì)象池,就可以讓生產(chǎn)和消費(fèi)復(fù)用固定數(shù)量的對(duì)象,很好的避免了頻繁創(chuàng)建銷毀對(duì)象以及內(nèi)存增長(zhǎng)不受控制的情況。
二、如何實(shí)現(xiàn)
1.確定接口
(1)、確定動(dòng)態(tài)關(guān)系
通過(guò)序列圖可以確定對(duì)象需要的接口,我們以socket服務(wù)為場(chǎng)景繪制序列圖,如下
(2)、確定靜態(tài)關(guān)系
根據(jù)上面的序列圖確定的接口繪制成類圖,如下:
2.轉(zhuǎn)成代碼
由于模塊規(guī)模小,接口也不多,所以就不展示進(jìn)一步細(xì)化設(shè)計(jì)了。因?yàn)楸疚闹v述的是C++實(shí)現(xiàn)對(duì)象池,所以將上述設(shè)計(jì)轉(zhuǎn)化為C++接口定義。如下:
/// <summary> /// 對(duì)象池 /// </summary> class ObjectPool { public: /// <summary> /// 構(gòu)造方法 /// </summary> /// <param name="bufferArray">對(duì)象池的緩沖區(qū),由外部指定,可以理解為一個(gè)數(shù)組。數(shù)組大小需滿足bufferSize>=elementSize*arraySize</param> /// <param name="elementSize">數(shù)組元素大小</param> /// <param name="arraySize">數(shù)組長(zhǎng)度或元素個(gè)數(shù)</param> ObjectPool(void* bufferArray, int elementSize, int arraySize ); /// <summary> /// 申請(qǐng)對(duì)象 /// 如果池里對(duì)象不足,則會(huì)等待,直到有對(duì)象才返回。 /// </summary> /// <returns>返回申請(qǐng)的對(duì)象指針</returns> void* Applicate(); /// <summary> /// 申請(qǐng)對(duì)象 /// </summary> /// <param name="timeout">超時(shí)時(shí)間,超時(shí)后返回null</param> /// <returns>返回申請(qǐng)的對(duì)象指針</returns> void* Applicate(int timeout); /// <summary> /// 歸還對(duì)象 /// </summary> /// <param name="element">需歸還的對(duì)象</param> void ReturnBack(void* element); };
三、完整代碼
根據(jù)上述的初步設(shè)計(jì),再進(jìn)行細(xì)化,以及實(shí)現(xiàn),最終得出如下代碼實(shí)現(xiàn)。
ObjectPool.h
#ifndef OBJECTPOOL_H #define OBJECTPOOL_H /************************************************************************ * @Project: AC::ObjectPool * @Decription: 對(duì)象池:“需求很大,但數(shù)量有限”的情況下,就需要對(duì)資源進(jìn)行專門(mén)的管理, *不斷的申請(qǐng)和釋放對(duì)象是不合理的(除非有專門(mén)的內(nèi)存管理機(jī)制,或明確的編譯優(yōu)化內(nèi)存復(fù)用)。 *這是一個(gè)對(duì)限定數(shù)量資源的復(fù)用管理模塊。 * @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> /// 對(duì)象池 /// </summary> class ObjectPool { public: /// <summary> /// 構(gòu)造方法 /// </summary> /// <param name="bufferArray">對(duì)象池的緩沖區(qū),由外部指定,可以理解為一個(gè)數(shù)組。數(shù)組大小需滿足bufferSize>=elementSize*arraySize</param> /// <param name="elementSize">數(shù)組元素大小</param> /// <param name="arraySize">數(shù)組長(zhǎng)度或元素個(gè)數(shù)</param> ObjectPool(void* bufferArray, int elementSize, int arraySize ); /// <summary> /// 析構(gòu)方法 /// </summary> ~ObjectPool(); /// <summary> /// 申請(qǐng)對(duì)象 /// 如果池里對(duì)象不足,則會(huì)等待,直到有對(duì)象才返回。 /// </summary> /// <returns>返回申請(qǐng)的對(duì)象指針</returns> void* Applicate(); /// <summary> /// 申請(qǐng)對(duì)象 /// </summary> /// <param name="timeout">超時(shí)時(shí)間,超時(shí)后返回null</param> /// <returns>返回申請(qǐng)的對(duì)象指針</returns> void* Applicate(int timeout); /// <summary> /// 歸還對(duì)象 /// </summary> /// <param name="element">需歸還的對(duì)象</param> void ReturnBack(void* element); /// <summary> /// 獲取對(duì)象池的緩沖區(qū),即構(gòu)造方法中的bufferArray /// </summary> /// <returns>緩沖區(qū)的指針</returns> void* GetPoolBuffer(); /// <summary> /// 獲取對(duì)象的大小,即構(gòu)造方法中的elementSize /// </summary> /// <returns>對(duì)象的大小</returns> int GetObjectSize(); /// <summary> /// 獲取總的對(duì)象數(shù)量,即構(gòu)造方法中的arraySize /// </summary> /// <returns>總的對(duì)象數(shù)量</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> /// 泛型對(duì)象池 /// </summary> /// <typeparam name="T">對(duì)象類型</typeparam> template<typename T> class ObjectPoolGeneric:private ObjectPool { public: /// <summary> /// 構(gòu)造方法 /// </summary> /// <param name="array">對(duì)象數(shù)組</param> /// <param name="size">數(shù)組大小</param> /// <returns></returns> ObjectPoolGeneric(T*array,int size) :ObjectPool(array, sizeof(T), size) { } /// <summary> /// 析構(gòu)方法 /// </summary> ~ObjectPoolGeneric() {} /// <summary> /// 申請(qǐng)對(duì)象 /// 如果池里對(duì)象不足,則會(huì)等待,直到有對(duì)象才返回。 /// </summary> /// <returns>返回申請(qǐng)的對(duì)象指針</returns> T* Applicate() { return (T*)ObjectPool::Applicate(); } /// <summary> /// 申請(qǐng)對(duì)象 /// </summary> /// <param name="timeout">超時(shí)時(shí)間,超時(shí)后返回null</param> /// <returns>返回申請(qǐng)的對(duì)象指針</returns> T* Applicate(int timeout) { return (T*)ObjectPool::Applicate(timeout); } /// <summary> /// 歸還對(duì)象 /// </summary> /// <param name="element">需歸還的對(duì)象</param> void ReturnBack(T* element) { ObjectPool::ReturnBack(element); } /// <summary> /// 獲取對(duì)象池的緩沖區(qū),即構(gòu)造方法中的bufferArray /// </summary> /// <returns>緩沖區(qū)的指針</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、對(duì)象復(fù)用,示例:
#include "ObjectPool.h" class A { public: A() { printf("%p\n", this); } }; int main(int argc, char** argv) { //初始化對(duì)象池,2個(gè)對(duì)象 AC::ObjectPool objectPool(new char[sizeof(A) * 2], sizeof(A), 2); A* a, * b, * c; //申請(qǐng)對(duì)象,使用定位new初始化對(duì)象 a = new (objectPool.Applicate())A; b = new (objectPool.Applicate())A; //歸還對(duì)象 a->~A();//返初始化對(duì)象 objectPool.ReturnBack(a); c = new (objectPool.Applicate())A; b->~A(); c->~A(); //使用結(jié)束,刪除緩存 delete objectPool.GetPoolBuffer(); return 0; }
輸出:
016502E9
016502E8
016502E9
2、簡(jiǎn)易的線程池,示例:
#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; }; //線程信息數(shù)組,數(shù)組長(zhǎng)度即線程池的線程數(shù) ThreadInfo _threadArray[3]; //對(duì)象池,使用線程信息數(shù)組初始化 AC::ObjectPoolGeneric<ThreadInfo>_threadPool(_threadArray, 3); bool _exitThreadPool = false; //在線程池中運(yùn)行方法 void RunInThreadPool(std::function<void()> f) { //申請(qǐng)線程 auto threadInfo = _threadPool.Applicate(); threadInfo->_threadPoc = f; if (threadInfo->pThread) //復(fù)用線程 { threadInfo->_cv.notify_one(); } else //創(chuàng)建線程 { threadInfo->pThread = new std::thread([=]() { while (!_exitThreadPool) { printf("thread %d run\n", threadInfo->pThread->get_id()); if (threadInfo->_threadPoc) { //執(zhí)行線程操作 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) { //在線程池中運(yùn)行方法 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
…
總結(jié)
以上就是今天要講的內(nèi)容,本文介紹了對(duì)象池的設(shè)計(jì)與實(shí)現(xiàn)以及使用,其使用場(chǎng)景其實(shí)不算多,因?yàn)楹芏嘈枰獙?duì)象復(fù)用的場(chǎng)景通常以及有底層實(shí)現(xiàn)了,比如線程池?cái)?shù)據(jù)庫(kù)的連接池等,所以本文講的內(nèi)容只能適用于少數(shù)的場(chǎng)景,比如waveOut播放音頻時(shí)是可以使用對(duì)象池實(shí)現(xiàn) 的。但總得來(lái)說(shuō),對(duì)象池還是有用的,所以將其寫(xiě)成博客用于記錄曾經(jīng)用過(guò)的技術(shù)。
到此這篇關(guān)于C++ 實(shí)現(xiàn)對(duì)象池的具體方法的文章就介紹到這了,更多相關(guān)C++ 對(duì)象池內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
OpenCV圖像旋轉(zhuǎn)Rotate的詳細(xì)介紹
這篇文章主要介紹了OpenCV圖像旋轉(zhuǎn)Rotate,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-05-05學(xué)習(xí)C語(yǔ)言要掌握的幾個(gè)庫(kù)
本文給大家分享的是網(wǎng)友提出的學(xué)習(xí)C語(yǔ)言要掌握的幾個(gè)庫(kù),這里分享給大家,有需要的小伙伴可以參考下。2015-07-07c++動(dòng)態(tài)庫(kù)調(diào)用的實(shí)現(xiàn)
本文主要介紹了c++動(dòng)態(tài)庫(kù)調(diào)用的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07Qt?自定義屬性Q_PROPERTY不顯示float類型的解決
這篇文章主要介紹了Qt?自定義屬性Q_PROPERTY不顯示float類型的問(wèn)題及解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11C語(yǔ)言實(shí)現(xiàn)動(dòng)態(tài)版通訊錄的代碼分享
這篇文章主要為大家詳細(xì)介紹了如何利用C語(yǔ)言實(shí)現(xiàn)一個(gè)簡(jiǎn)單的動(dòng)態(tài)版通訊錄,主要運(yùn)用了結(jié)構(gòu)體,一維數(shù)組,函數(shù),分支與循環(huán)語(yǔ)句等等知識(shí),需要的可以參考一下2023-01-01C++ 設(shè)置和獲取當(dāng)前工作路徑的實(shí)現(xiàn)代碼
這篇文章主要介紹了C++ 設(shè)置和獲取當(dāng)前工作路徑的實(shí)現(xiàn)代碼,防止DLL加載不到配置和文件,需要的朋友可以參考下2017-09-09