|
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.
| You must Sign In to use this message board. |
|
| | Msgs 1 to 24 of 24 (Total in Forum: 24) (Refresh) | FirstPrevNext |
|
|
 |
|
|
 |
|
|
First of all, thank you for writing this very nice article.
I just noticed that the DeletePool method does not delete the objects created in the objectHolder lists(free list and reserved list). Correct me if I am wrong.
Cheers,
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Valgrind memcheck confirms my doubts.
It can be simply fixed by adding this line:
delete m_pObj;
into the ObjectHolder::~ObjectHolder(){ if(m_pObj){ m_pObj->Release(); //here delete m_pObj; } m_pObj=NULL;
}
Cheers
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Nice article, though I have one query regarding implementation of everything inside .h files ?. why can't we have code separation in cpp and .h files.
-- modified at 5:34 Wednesday 14th March, 2007
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Thanks for your complement. It can be done. This is just a sample code. Also with .h files, it's easier to integrate aprticular with template base code.
|
| Sign In·View Thread·PermaLink | 2.00/5 (1 vote) |
|
|
|
 |
|
|
 |
|
|
Firstly, thanks.. This was just what i was looking for. In using this framework i have come across two small bugs.
firstly if the pool size is 1 then the function ProcessExpiredObjects() crashes when searching for expired objects.. This is because the erase method returns the iterator to the next object, and then the for loop also increments which moves the iterator on again (in this case past the end!). The new function looks like this:
void ProcessExpiredObjects() { ObjList::iterator it = m_oReserved.begin(); while( it != m_oReserved.end() ) { ObjHolder &oHolder = *it; if((long)time(NULL) - oHolder.GetTimeStamp() > m_nExpirationTime) { // this obj has exceeded the wait time
T *pObj = oHolder.GetObj(); if(ValidateObject(pObj) == true) { m_oFree.push_back(oHolder); oHolder.SetObject(NULL); std::cout << "Found expired Object. " << std::endl; } it = m_oReserved.erase(it); } else ++it; } }
secondly in the Checkout() function after ProcessExpiredObjects() is called then FindObject() gets called which returns an object you still return NULL instead of the object ref. i have just added the following lines after FindObject()
pObj = FindObject();
if (pObj != NULL) return pObj;
PS... you get my five
ade me; while(myKitchen.beerInFridge() == true) { me.watchTV(); me.consumeBeer(myKitchen.getBeerCan()); }
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Inside the Checkout() function, you have the follwing code: [...]
}else if(m_nExpirationTime != -1){ while(!pObj) { ProcessExpiredObjects(); pObj = FindObject(); sleep(m_nWaitTime); } }
but since the gaurd(m_mData) is locked, other threads cannot do a checkin!
To resolve I think you can unlock before the sleep and lock again after.
--- Count Dew
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi, I'm attempting to do this mutex unlock as suggested, but I am unclear as to the proper syntax to unlock (before sleeping) and re-lock afterwards. Fairly new to template programming, so I think my confusion is there, but I have tried several syntaxes without success. Thanks!
|
| Sign In·View Thread·PermaLink | 2.00/5 (1 vote) |
|
|
|
 |
|
|
hi thanks for this excellent article ! it looks very useful and i'm going to use it !(if u don't mind... ) little question: when i call ResetPool() it causes N destructor calls of ObjectHolder which uses Release method of its T pointer data member. since this pointer allocated by operator new , i think operator delete should be used (or i missed something). how the memory of m_pObj freed ? i'm a new programmer and i'll glad to recieve some explanations.
Thanks Sagiv.
|
| Sign In·View Thread·PermaLink | 1.00/5 (1 vote) |
|
|
|
 |
|
|
 |
|
|
In the class "PoolMgr", you have the function "Init()" defined twice, and in the class "GenericObject", you have the statement, "return true;" appearing outside the function, "MakeUsable".
While your article does have merit, there are some things you "squeaked" by without the benefit of further explanation (e.g. When is the Singleton object, "PoolMgr" going to destroy itself, since this is the surest way to avoid 'resource leaks'? [See Alexandrescu "Modern C++ Design" page 133, fourth paragraph.] Instead, you made your point by showing "manual" deletion of the resources to support resetting of the pool. Even so, you did not use an automatic deletion mechanism to take care of resource deletion. You used "time out" as a sort of signal for deletion, followed by the manual method of doing it.)
There is nothing wrong with how you wrote your article, it's a first step is how I would consider it. An excellent "first step" nonetheless.
I haven't read your follow-up article as yet, but I will, and I shall be looking to see how you handle some of the other points that would describe a more comprehensive manner in dealing overall with Policy-Based Designs.
Good job overall.
William
Fortes in fide et opere!
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
>>>>Good observation and thanks for your comments.
In the class "PoolMgr", you have the function "Init()" defined twice, and in the class "GenericObject", you have the statement, "return true;" appearing outside the function, "MakeUsable".
>>>> If you down load the code, it don’t have the Init() function as well as “return true” outside the function. I had also noticed this but before I update the article, it wasv moved out from “unedited Reader Contributions” section. So I could not modify it.
While your article does have merit, there are some things you "squeaked" by without the benefit of further explanation (e.g. When is the Singleton object, "PoolMgr" going to destroy itself, since this is the surest way to avoid 'resource leaks'? [See Alexandrescu "Modern C++ Design" page 133, fourth paragraph.] Instead, you made your point by showing "manual" deletion of the resources to support resetting of the pool. Even so, you did not use an automatice deletion mechanism to take care of resource deletion. You used "time out" as a sort of signal for deletion, followed by the manual method of doing it.)
>>>> I agree with your point. But when I wrote the article, I concentrated more on Pool functionality in generic way rather than singleton pattern. The main aim of this article is to show the reusable pool design with the power of templates.
There is nothing wrong with how you wrote your article, it's a first step is how I would consider it. An excellent "first step" nonetheless.
I haven't read your follow-up article as yet, but I will, and I shall be looking to see how you handle some of the other points that would describe a more comprehensive manner in dealing overall with Policy-Based Designs.
>>>> Here is the link for my new article :http://www.codeproject.com/useritems/Generic_Pool_Design.asp Here, again I have concentrated more on policy based design to achieve different requirements about the pool rather than singleton pattern. I have used Loki::SingletonHolder<> to create singleton instance of the class instead of reinventing the wheel.
Good job overall.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
if you could come up with a VC 6 version for those members who are still using VC 6.
Because I am one of them who still uses that version, I only have your code from which to follow what your wonderful sample achieves, without the added benefit of seeing it work (which limits my ability to conduct any kind of experimenting with it).
Nevertheless, it's a VERY GOOD!! article and not only am I voting a '5' for it, but am looking forward to the next one you propose writing.
Excellent piece of work!!
William
Fortes in fide et opere!
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
If you create new project in VC6 and add all these header and CPP files, it should compile and work fine because I developed this application on Linux with GCC. Only you might need to look at the Sleep() function if it is different in VC6 verse VC7. I don't see any reason if microsoft has changed that. Look for below code in PoolMgr.h
#ifdef WIN32 // WINDOWS #include // Here Windows uses Sleep() verse linux uses sleep(). More over, Windows Sleep() takes parameter in Millisec while Linux sleep() takes into secs. #define sleep(x) Sleep(x * 1000)
#else // UNIX #include #endif
Let me know if don't work. I will also try to verify this. Thx.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I have submitted my article "Generic Pool :policy based design" http://www.codeproject.com/useritems/Generic_Pool_Design.asp I am sorry, I could not provide the demo version for VC6 as I don't have IDE avaiable.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
This is really nice article and handy code for pool implementation. It would have been nice if you have provided multithreading support.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I have plan to provide support for Multithreading as well as sweeper thread which will cleanup the expired resources. In my next article, Generic Pool using Policy based design, I will have this support. thanks for your comment.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
There is no need to write a 'sweep up' implementation to tidy up based on some regular time or indeed by running the PoolMgr in a separate thread; just tidy up each time a request is made for a pool entry. Of course, if you are worried about freeing resources used by a pool entry then a sweeper would be useful, but would require a link between the PoolMgr and the stored objects. If this kind of integration is available then you could also provide notification to the pooled objects of when they have been locked/released/forcibly released.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
Hi,
This could have been a very interesting article, but now it's just a zip file with code... It would have been nice if you would have described the design tradeoffs you made and the interface of the Pool template, along with a simple example maybe. Eg is it possible to change the pool size at runtime? If no, why did do choose this design? Etc...
|
| Sign In·View Thread·PermaLink | 5.00/5 (1 vote) |
|
|
|
 |
|
|
I have updated this article with snippet of the code and a sample usage. It has a function call Init(..) to Initialize the Pool which can be modified to support the changing the size of the pool at runtime.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
 |
|
|
General News Question Answer Joke Rant Admin
|