对象池模式 C++实现
对象池模式(The Object Pool Pattern)
对象池是一种对象复用技术。Mark Grand在《Patterns in Java》描述了对象池的设计模式,它通过管理和复用有限对象来共享某些稀少或必须付出昂贵代价的资源,该模式的UML结构分为3部分。
Reusable:被复用的对象或资源。
Client:复用对象的使用者,1个Client对象可以使用多个复用对象,一个复用对象只能同时被1个Client使用。
ReusablePool:对象池即复用对象的容器或集合,用来管理和存储可复用的对象。一般来说为保证对象复用的安全,对象池将按照Singleton的模式设计为全局唯一实例。
该模式被广泛使用,出现了连接池、线程池等许多应用。
理解对象池模式
对象池模式管理一个可替代对象的集合。组件从池中借走对象,用它来完成一些任务并当任务完成时归还该对象。被归还的对象接着满足请求,不管是同一个组件还是其他组件的请求。对象池模式可以管理那些代表现实资源或者通过重用来分摊昂贵初始化代价的对象。
使用对象池
对象池技术可以避免在程序的生命周期中创建和删除大量对象。如果知道程序需要同一类型的大量对象,而且对象的生命期都很短,就可以为这些对象创建一个池(pool)进行缓存。只要代码中需要一个对象,就可以向对象池请求。用完此对象时,要把它放回到池中。对象池只创建一次对象,因此他们的构造函数只调用一次,而不是每次使用都调用。因此,当构造函数要完成一些设置动作,而且这些设置可以应用于该对象的多次使用时,对象池就很合适。另外在非构造函数方法调用中要在对象上设置特定于实例的参数时,也很适用于对象池。
一个对象池实现
对象池类模板的实现。这个池在构造时会分配一大块(chunk)指定类的对象(块可理解为包括许多对象,即一堆对象),并通过acquireObject()方法交出对象。当客户用完这个对象时,会通过releaseObject()方法将其返回。如果调用了acquireObject,但是没有空闲的对象,池会分配另一块对象。
对象池实现中最难的一方面是要记录那些对象是空闲的,那些对象正在使用,这个实现采用了以下做法,即把空闲的对象保存在一个队列中。每次客户请求一个对象时,池就会把队列中的第一个对象交给该客户。这个池不会显式地跟踪正在使用的对象。它相信客户在用完对象后会正确地将对象交还到池中。另外,这个池会在一个像两种记录所有已分配的对象。这个向量尽在撤销池时才会用到,以便释放所有对象的内存,从而避免内存泄漏。
代码示例:
#include <queue> #include <vector> #include <memory> #include <stdexcept> #include <algorithm>
using namespace std;
//#define kDefaultChunkSize 10
template <typename T> class ObjectPool { public: ObjectPool(int chunkSize = kDefaultChunkSize) throw(invalid_argument, bad_alloc);
~ObjectPool();
T& acquireObject();
void releaseObject(T& obj);
protected: //m_FreeList stores the objects that are not currently in use by clients queue<T*> m_FreeList; //m_AllObjects stores pointers to all the objects, in use or not. //This vector is needed in order to ensure that all objects are freed properly in the destructor vector<T*> m_AllObjects; int m_ChunkSize; static const int kDefaultChunkSize = 10;
//Allocates m_ChunkSize new objects and adds them to the m_FreeList void allocateChunk(); static void arrayDeleteObject(T* obj);
private: ObjectPool(const ObjectPool<T>& src); ObjectPool<T>& operator=(const ObjectPool<T>& rhs);
};
template <typename T> const int ObjectPool<T>::kDefaultChunkSize;
template <typename T> ObjectPool<T>::ObjectPool(int chunkSize) throw(invalid_argument, bad_alloc) : m_ChunkSize(chunkSize) { if (m_ChunkSize <= 0) { throw invalid_argument("chunk size must be positive"); } //create m_ChunkSize objects to start. allocateChunk(); }
//在连续的存储空间中分配m_ChunkSize个元素, //它会在vector中存储对象数组的指针,并把每个对象压至queue中。 template <typename T> void ObjectPool<T>::allocateChunk() { T* newObjects = new T[m_ChunkSize]; m_AllObjects.push_back(newObjects); for (int i = 0; i < m_ChunkSize; i++) { m_FreeList.push(&newObjects[i]); } }
//Freeing function for use in the for_each algorithm in the destructor template <typename T> void ObjectPool<T>::arrayDeleteObject(T* obj) { delete [] obj; }
template <typename T> ObjectPool<T>::~ObjectPool() { for_each(m_AllObjects.begin(), m_AllObjects.end(), arrayDeleteObject); }
//返回空闲列表中的队头对象,如果没有空闲对象则首先调用allocateChunk template <typename T> T& ObjectPool<T>::acquireObject() { if (m_FreeList.empty()) { allocateChunk(); } T* obj = m_FreeList.front(); m_FreeList.pop(); return (*obj); }
//将对象返回到空闲列表的队尾 template <typename T> void ObjectPool<T>::releaseObject(T& obj) { m_FreeList.push(&obj); } |
使用对象池
考虑一个要从用户得到请求并处理这些请求的应用。这个应用很可能是图形前端和后端数据库之间的一个中间件。可以把每个用户请求编码到一个对象中。
代码示例:
//使用对象池,用户请求 class UserRequest { public: UserRequest() {} ~UserRequest() {}
//Methods to populate the request with specific information //Methods to retrive the request data
private: //data members
};
//客户 class Client { public: Client() {} ~Client() {}
UserRequest& obtainUserRequest(ObjectPool<UserRequest>& pool); void processUserRequest(ObjectPool<UserRequest>& pool, UserRequest& req); private:
};
UserRequest& Client::obtainUserRequest(ObjectPool<UserRequest>& pool) { cout << "Client::obtainUserRequest: " << pool.getCount() << endl; UserRequest& request = pool.acquireObject(); sleep(10); //Populate the request with user input return request; }
void Client::processUserRequest(ObjectPool<UserRequest>& pool, UserRequest& req) { //Process the request cout << "Client::processUserRequest: " << pool.getCount() << endl; pool.releaseObject(req); } int main() { ObjectPool<UserRequest> requestPool(1000);
while (/*program is running*/) { UserRequest& req = obtainUserRequest(requestPool); processUserRequest(requestPool, req); } } |