什么是對象池
對象池是一種空間換時間的技術,對象被預先創建並初始化后放入對象池中,對象提供者就能利用已有的對象來處理請求,並在不需要時歸還給池子而非直接銷毀
它減少對象頻繁創建所占用的內存
空間和初始化時間
對象池原理
描述一個對象池有兩個很重要的參數,一個是這個對象池的類型,另一個是這個對象池可以獲得對象的數量
對象池的實現和內存池的實現原理很像:都是一開始申請大內存空間,然后把大內存分配成小內存空間,當需要使用的時候直接分配使用,不在向系統申請內存空間,也不直接釋放內存空間。使用完之后都是放回池子里
不同的地方在內存池有一個映射數組,在使用時負責快速定位合適的內存池(一個內存池可以有很多內存塊大小不同的池子)
但是每一個類型的對象只對應一個對象池,並自己管理自己的對象池。不同類型的對象池是相互獨立的存在
對象池的優點
1、減少頻繁創建和銷毀對象帶來的成本,實現對象的緩存和復用
2、提高了獲取對象的響應速度,對實時性要求較高的程序有很大幫助
3、一定程度上減少了垃圾回收機制(GC)的壓力
對象池的缺點
1、很難設定對象池的大小,如果太小則不起作用,過大又會占用內存資源過高
2、並發環境中, 多個線程可能(同時)需要獲取池中對象, 進而需要在堆數據結構上進行同步或者因為鎖競爭而產生阻塞, 這種開銷要比創建銷毀對象的開銷高數百倍;
3、由於池中對象的數量有限, 勢必成為一個可伸縮性瓶頸;
4、所謂的臟對象就是指的是當對象被放回對象池后,還保留着剛剛被客戶端調用時生成的數據。
臟對象可能帶來兩個問題
- 臟對象持有上次使用的引用,導致內存泄漏等問題。
- 臟對象如果下一次使用時沒有做清理,可能影響程序的處理數據。
什么條件下使用對象池
1、資源受限的, 不需要可伸縮性的環境(cpu\內存等物理資源有限): cpu性能不夠強勁, 內存比較緊張, 垃圾收集, 內存抖動會造成比較大的影響, 需要提高內存管理效
率,響應性比吞吐量更為重要;
2、數量受限的, 比如數據庫連接;
3、創建對象的成本比較大,並且創建比較頻繁。比如線程的創建代價比較大,於是就有了常用的線程池。
對象池實現過程
對象池的實現代碼分三部分,一個是對象池的內部實現類(ObjectPool),另一個是管理對象池的類(ObjectPoolBase)
對象池內部的實現和內存池基本一樣,都是先申請一大塊內存空間,然后再把大內存分成許多小內存,每個小內存對應一個對象,這些小內存以鏈表的形式進行管理
內存池的管理主要是對用戶提供接口,參數的傳遞通過可變參的模板類來實現,對new和delete操作進行封裝是為了更方便的初始化對象,讓使用時更加靈活;要
創建不同類型的對象池,只需要繼承ObjectPoolBase這個類就可以,當發現該類型對應的對象池沒有初始化的時候,就會創建該對象池並且進行初始化操作,之后
就可以正常分配對象
注意:
對象池和內存池可以一起使用,在一起使用的情況下,如果要在對象池中使用智能指針,應該特別注意new操作,因為智能指針使用的是全局的new,內存池使用的
是類里面的new,在申請內存時會有差別
對象池文件

#ifndef _ObjectPoolBase_hpp_ #define _ObjectPoolBase_hpp_ #include<stdlib.h> #include<assert.h> #include<mutex> #ifdef _DEBUG #ifndef xPrintf #include<stdio.h> #define xPrintf(...) printf(__VA_ARGS__) #endif #else #ifndef xPrintf #define xPrintf(...) #endif #endif // _DEBUG //模板給對象池提供參數接口 template<class Type, size_t nPoolSzie> //對象池的實現 class CELLObjectPool { public: CELLObjectPool() { initPool(); } ~CELLObjectPool() { if (_pBuf) delete[] _pBuf; } private: //NodeHeader是每個對象的描述信息 class NodeHeader { public: //下一塊位置 NodeHeader* pNext; //內存塊編號 int nID; //引用次數 char nRef; //是否在內存池中 bool bPool; private: //預留 char c1; char c2; }; public: //釋放對象內存 void freeObjMemory(void* pMem) { //首地址往前偏移,對象和對象描述信息的內存一起釋放 NodeHeader* pBlock = (NodeHeader*)((char*)pMem - sizeof(NodeHeader)); xPrintf("freeObjMemory: %llx, id=%d\n", pBlock, pBlock->nID); assert(1 == pBlock->nRef); //內存在對象池的部分釋放之后,指針回到第一個未分配的對象 if (pBlock->bPool) { std::lock_guard<std::mutex> lg(_mutex); if (--pBlock->nRef != 0) { return; } pBlock->pNext = _pHeader; _pHeader = pBlock; } else { if (--pBlock->nRef != 0) { return; } //不在對象池的直接釋放內存 delete[] pBlock; } } //申請對象內存,優先向內存池申請內存,內存池不夠在向系統申請內存 void* allocObjMemory(size_t nSize) { std::lock_guard<std::mutex> lg(_mutex); NodeHeader* pReturn = nullptr; //如果對象池已經滿了,數量達到上限,需要向系統申請內存 if (nullptr == _pHeader) { //計算對象池大小=對象數量*(一個對象內存大小+對象描述信息內存大小) pReturn = (NodeHeader*)new char[sizeof(Type) + sizeof(NodeHeader)]; pReturn->bPool = false; pReturn->nID = -1; pReturn->nRef = 1; pReturn->pNext = nullptr; } else { pReturn = _pHeader; _pHeader = _pHeader->pNext; assert(0 == pReturn->nRef); pReturn->nRef = 1; } xPrintf("allocObjMemory: %llx, id=%d, size=%d\n", pReturn, pReturn->nID, nSize); return ((char*)pReturn + sizeof(NodeHeader)); } private: //初始化對象池 void initPool() { //斷言 assert(nullptr == _pBuf); //對象池不為空(已經初始化過),不初始化直接返回 if (_pBuf) return; //計算對象池大小=對象數量*(一個對象內存大小+對象描述信息內存大小) size_t realSzie = sizeof(Type) + sizeof(NodeHeader); size_t n = nPoolSzie * realSzie; //申請池的內存 _pBuf = new char[n]; //初始化內存池 _pHeader = (NodeHeader*)_pBuf; _pHeader->bPool = true; _pHeader->nID = 0; _pHeader->nRef = 0; _pHeader->pNext = nullptr; //遍歷內存塊進行初始化 NodeHeader* pTemp1 = _pHeader; for (size_t n = 1; n < nPoolSzie; n++) { NodeHeader* pTemp2 = (NodeHeader*)(_pBuf + (n* realSzie)); pTemp2->bPool = true; pTemp2->nID = n; pTemp2->nRef = 0; pTemp2->pNext = nullptr; pTemp1->pNext = pTemp2; pTemp1 = pTemp2; } } private: //描述信息塊地址 NodeHeader* _pHeader; //對象池內存緩存區地址 char* _pBuf; //多線程使用需要加鎖 std::mutex _mutex; }; //模板類給主函數使用對象池提供接口 template<class Type, size_t nPoolSzie> //使用對象池的類 class ObjectPoolBase { public: //重載給對象申請內存的new操作 void* operator new(size_t nSize) { return objectPool().allocObjMemory(nSize); } //重載給對象釋放內存的delete操作 void operator delete(void* p) { objectPool().freeObjMemory(p); } //創建對象,用模板類提供對外的可變參數的接口 //這里的模板為啥要用可變參數? //這里創建一個對象的時候還可能帶有初始化的參數,這個參數的個數是不一樣的,所有模板的參數需要變參 // template<typename ...Args> static Type* createObject(Args ... args) { //不定參數 可變參數 Type* obj = new Type(args...); //可以做點其它的事情,比如說對象的放逐和驅逐 return obj; } //銷毀對象 static void destroyObject(Type* obj) { delete obj; } private: //定義不同類型的對象池 typedef CELLObjectPool<Type, nPoolSzie> ClassTypePool; //單例-餓漢模式,實例化一個對象池 static ClassTypePool& objectPool() { //靜態CELLObjectPool對象 static ClassTypePool sPool; return sPool; } }; #endif // !_ObjectPoolBase_hpp_
測試文件
#include<stdlib.h> #include<iostream> #include<thread> #include<mutex>//鎖 #include<memory> #include"ObjectPoolBase.hpp" using namespace std; //10是池內對象的最大數量,通過繼承ObjectPoolBase來使用對象池 class ClassA : public ObjectPoolBase<ClassA, 10> { public: ClassA(int n) { num = n; printf("ClassA\n"); } ~ClassA() { printf("~ClassA\n"); } public: int num = 0; }; class ClassB : public ObjectPoolBase<ClassB, 10> { public: ClassB(int n, int m) { num = n * m; printf("ClassB\n"); } ~ClassB() { printf("~ClassB\n"); } public: int num = 0; }; int main() { { //對象池的使用過程是在使用new的時候,因為你要創建的類繼承了對象池管理的類, //對象池管理類會根據你設計的對象數量創建(初始化)對象池, //進入對象池的時候首先判斷該對象池是否初始化,初始化過說明該對象池已經存在 //未初始化說明還沒有該類型的對象池 //如果該對象池沒有初始化,就進行初始化(創建一個對象池) //已經初始化就直接從該對象池中分配一個對象 //然后通過new操作在對象池中分配一個已經創建好的對象 //再根據對象傳進來的參數,確定對象池允許有幾個對象 //然后申請相應大小的內存,逐個以鏈表的形式進行初始化 //兩種創建對象的方法 //直接用new創建對象,把new和delete暴露在外,使用不靈活,初始化對象不方便 ClassA* a1 = new ClassA(5); //delete a1; //把new和delete封裝成一個函數,可以更靈活的對對象進行操作,比如在創建的時候初始化對象 ClassA* a2 = ClassA::createObject(6); //ClassA::destroyObject(a2); delete a1; ClassA::destroyObject(a2); ClassB* b1 = new ClassB(5, 6); delete b1; ClassB* b2 = ClassB::createObject(5, 6); ClassB::destroyObject(b2); } system("pause"); return 0; }