Introduction
Many applications use connection/object pool. A program may require a IMAP connection pool and LDAP connection pool. One could easily implement an IMAP connection pool, then take the existing code and implement a LDAP connection pool. The program grows, and now there is a need for a pool of threads. So just take the IMAP connection pool and convert that to a pool of threads (copy, paste, find, replace????). Need to make some changes to the pool implementation? Not a very easy task, since the code has been duplicated in many places. Re-inventing source code is not an intelligent approach in an object oriented environment which encourages re-usability. It seems to make more sense to implement a pool that can contain any arbitrary type rather than duplicating code. How does one do that? The answer is to use type parameterization, more commonly referred to as templates.
C++ templates allow one to implement a generic Pool<T>
template that has a type parameter T
. T
can be replaced with actual types, for example, class ImapConn
, and C++ will generate the class Pool<ImapConn>
. Changing the implementation of the Pool becomes relatively simple. Once the changes are implemented in the template Pool<T>
, they are immediately reflected in the classes Pool<ImapConn>
, Pool<LdapConn>
, and Pool<Threads>
.
Attached demo project contains:
- Docs: Source code documentation.
- Source code and project files.
This article demonstrates how to implement generic pool using templates. Code is been compiled on Windows as well as Linux. Please feel free to modify and use.
Below are the requirements to implement generic pool:
- Generic: It should be generic enough to work with any type of resources. E.g., Database Connection pool, Thread pool, other resource pool etc...
- Pool Size: Size of the pool should be configurable and if required changeable at runtime.
- Pool Type: If Pool is fixed size, or temporary connection allowed in case of pool is full.
- Object's lifetime: If user doesn't check-in the resource back, what should be the duration at which the object will be considered as expired and returned back to free resources.
- Timeout functionality: If Pool is full and temporary connections are not allowed, how long caller function can wait to get object.
The source code contains:
- PoolMgr.h which implements the singleton Pool class. It has following functions:
template<class T>
class PoolMgr
{
typedef ObjectHolder<T> ObjHolder;
typedef list<ObjHolder> ObjList;
static Mutex m_mPool;
Mutex m_mData;
public:
static PoolMgr<T>* GetInstance()
{
if(!m_pPoolMgr) {
Lock<Mutex> gaurd(m_mPool);
if(!m_pPoolMgr)
m_pPoolMgr = new PoolMgr<T>()
}
return m_pPoolMgr;
}
static void DeletePool()
{
if(m_pPoolMgr) {
Lock<Mutex> gaurd(m_mPool);
if(m_pPoolMgr){
delete m_pPoolMgr;
m_pPoolMgr = NULL;
}
}
void Init(unsigned nPoolSize, long nExpirationTime,
bool bTempObjAllowed, unsigned nWaitTime = 3)
{
�
}
void ResetPool()
{
.....
}
void Init(unsigned nPoolSize, long nExpirationTime,
bool bTempObjAllowed, unsigned nWaitTime = 3)
{
...
}
T* Checkout()
{
...
}
void Checkin(T *pObj)
{
...
}
private:
static PoolMgr<T> *m_pPoolMgr;
PoolMgr()
{
m_nPoolSize = 0;
m_nExpirationTime = 600;
m_bTempObjAllowed = true;
m_nWaitTime = 3;
}
~PoolMgr()
{
}
unsigned m_nPoolSize;
unsigned m_nWaitTime;
long m_nExpirationTime;
bool m_bTempObjAllowed;
ObjList m_oReserved;
ObjList m_oFree;
};
template<class T> PoolMgr<T>* PoolMgr<T>::m_pPoolMgr = NULL;
template<class T> Mutex PoolMgr<T>::m_mPool;
static PoolMgr<T>* GetInstance()
: which returns the instance of PoolMgr
.
static void DeletePool()
: deletes the pool and frees resources.
void Init(unsigned nPoolSize, long nExpirationTime, bool bTempObjAllowed, unsigned nWaitTime)
: User must initialize the Pool with the following parameters:
PoolSize
: Size of the Pool.
ExpirationTime
: Duration in seconds. If object is not used for this duration, object would be considered as expired and would be moved to an available object pool.
TempConnAllowd
: If Pool is full, should Pool be allowed to create temporary connections.
WaitTime
: If temporary connection is not allowed and Pool is full, how long caller function can wait to get the connection from the expired connections.
void ResetPool()
: Release all the resources and reset the pool.
T* Checkout()
: Check out the resource.
void Checkin(T* pObj)
: Check in the resource.
template<class T>
: Class PoolMgr
contains two list of pools. One is for reserved objects and the other for free objects. In a multithreaded environment, it would avoid locking all the objects instead of specific types. E.g., only all free objects or reserved.
- ObjectHolder.h: which contains the object pointer and timestamp. It is a template class of type
T
which allows storing any generic object class. template<class T>
class ObjectHolder
{
public:
ObjectHolder()
{
m_nTimeStamp = -1;
m_pObj = NULL;
}
~ObjectHolder()
{
if(m_pObj) {
m_pObj->Release();
m_pObj = NULL;
}
}
void InitObject()
{
if(!m_pObj) {
m_pObj = new T();
m_pObj->Init();
}
}
private:
T *m_pObj;
long m_nTimeStamp;
};
- GenericObject.h: This is a sample generic class which is used for testing this pool. User of this pool needs to either implement following methods in their connection/object class or inherit from
GenericObject
class. class GenericObject
{
public:
GenericObject() {}
~GenericObject() {}
virtual void Init() {}
virtual void Release() {}
virtual bool IsUsable()
{
return true;
}
virtual bool MakeUsable()
{
if(!IsUsable()) {
Init();
return true;
}
};
Here:
void Init()
: Initialize the object. If object needs to make connection, do it in this function.
void Release()
: Release the resources.
bool IsUsabled()
: Is this object still usable?
bool MakeUsable()
: If it is not usable, try to make it usable, and if successful, return true
. This will avoid construction of new object if successfully made reusable.
- MutexWin.h (Windows) and Mutex.h (Linux):
template <class T > class Lock
{
T& obj_;
public:
Lock(T& obj):obj_(obj)
{
obj_.Lock();
}
~Lock()
{
obj_.Unlock();
}
};
class Mutex
{
public:
Mutex()
{
InitializeCriticalSection(&m_mMutex);
}
virtual ~Mutex()
{
DeleteCriticalSection(&m_mMutex);
}
bool Lock()
{
EnterCriticalSection(&m_mMutex);
return true;
}
bool Unlock()
{
LeaveCriticalSection(&m_mMutex);
return true;
}
private:
CRITICAL_SECTION m_mMutex;
void operator=(Mutex &m_mMutex) {}
Mutex( const Mutex &m_mMutex ) {}
};
- main.c: This is a sample
main
function which gets the instance of PoolMgr
and checks out and checks in the GenericObject
. PoolMgr<GenericObject> *pMgr = PoolMgr<GenericObject>::GetInstance();
if(pMgr)
{
pMgr->Init(10,600, false);
GenericObject *pObj = NULL;
pObj = pMgr->Checkout();
pMgr->Checkin(pObj);
pMgr->ResetPool();
pMgr->Init(1,10, false);
GenericObject *pObj = NULL;
pObj = pMgr->Checkout();
std::cout << "1st Object checked out " << std::endl;
pObj = pMgr->Checkout();
std::cout << "2st Object checked out after 10 secs " << std::endl;
pMgr->ResetPool();
}
PoolMgr<GenericObject>::DeletePool();
Please see my other article about pool design: Generic Pool: Policy based design.
History
- 09/16/2004: Added support for thread synchronization.
Please let me know how this article would have been improved and made more useful.