Advertisment

Designing Threaded Applications

author-image
PCQ Bureau
New Update

This month, we’ll create a worker thread class and use it to design a generic worker thread pool that can

be used in any application where multiple client requests require similar handling

Advertisment

The basic idea behind designing a worker thread class is as follows: upon instantiation, the class creates a specified number of threads, the number being specified by the programmer or calculated at runtime by the application. Since these threads constitute the pool from which a thread will be chosen to entertain a client request, they can be in any of the following three states:

Free: The threads are in this state upon their creation during the class instantiation, and when they have completed entertaining a client request 

Busy: Threads enter this state when they have accepted the client request and have started to service it 



Termination: Threads enter this state to end themselves when signaled to do so. When in this state, they shall not be able to service any incoming client request 

Advertisment

For each thread created in the pool, we shall maintain some information specific to it, like the thread ID, thread handle, its current working status, and so on, in order to have complete control over the thread, and to monitor its status. To maintain all this information, we create a class as follows:

class CThreadData



{


public:


// member variables


HANDLE hFree, hBusy, hTerminate, hThread;


DWORD index, ThreadID,Used;


DWORD data;





// inline member functions


CThreadData()


{


// initialize events for indicating thread status


// we are free upon startup...


hFree=CreateEvent(NULL,TRUE,TRUE,NULL); 


// ... busy event is not signaled hBusy=CreateEvent(NULL,TRUE,FALSE,NULL);


// ... and so is the termination event


hTerminate=CreateEvent(NULL,TRUE,FALSE,NULL);


ThreadID=0;


Used=0;


}


~CThreadData()


{


// set ourselves as busy so that no more incoming requests


// are assigned to us...


SetEvent(hBusy);


//...and signal the termination flag


SetEvent(hTerminate);


// wait for 2 seconds to let thread terminate itself


if (WaitForSingleObject(hThread,2000)==WAIT_TIMEOUT)


{


printf(“Killing thread %ld\n”,index); TerminateThread(hThread,1);


}


// close all open handles...


if (hThread)CloseHandle(hThread);


if (hFree)CloseHandle(hFree);


if (hBusy)CloseHandle(hBusy);


if (hTerminate)CloseHandle(hTerminate);


}


};





As can be seen, the constructor of the class creates three events, one for each of the previously mentioned states. Since this class is instantiated at the time of the creation of the thread (thus, each thread in the pool has a CthreadData object associated with it), initially the thread is marked as free for use by signaling the free event (hFree). This is achieved by setting the third parameter (which indicates whether the event is initially owned at the time of creation) of CreateEvent API to TRUE. Similarly, the busy event (hBusy) and the termination event (hTerminate) are created but not signaled, that is., they are not set. Also, the thread ID and the count of times this thread has been used are both set to zero.






































Now, when a thread terminates, all associated data will also be removed from the system memory, that is, CThreadData object associated with the thread will be destroyed. Hence, in the class destructor, we first signal the busy event. This is done because while this thread is terminating, the operating system can always context switch to the thread. This will prevent the thread from being selected for servicing incoming client requests. The next step is signaling the termination event and waiting for two seconds for the thread to perform cleanup and terminate. Part of the cleanup is closing all associated handles. However, if the thread doesn’t perform the cleanup within two seconds, it is terminated forcefully using the TerminateThread API. 

Advertisment

Now let’s see how the worker thread pool is created using the following class:

class CThreadPool



{


// member variables


CThreadData *TData;


DWORD ThreadCount, Next;


public:


// inline member functions


// the constructor gets the number of threads to be present in the pool


// alongwith a array of pointers to functions to be used by each of the threads


CThreadPool(DWORD cntThreads, DWORD (*pTFunc<>)(LPVOID))


{


// create an array of CThreadData objects for each thread in the pool


TData = new CThreadData;


if (TData==NULL)


{


printf(“unable to create thread details data!\n”);


throw -1;


}


// create the worker threads..


for(DWORD i=0;i
{



TData.hThread=CreateThread(NULL,0,


(LPTHREAD_START_ROUTINE)pTFunc, (LPVOID)&TData,0,&TData.ThreadID);


TData.index=i+1;


}


// save thread count...


ThreadCount=cntThreads;





// set the initial starting point to search for the next free thread...


Next=0;


}


~CThreadPool()


{


// show the number of times each thread was used


for(DWORD i=0;i
printf(“%ld used %ld times\n”,i+1,TData.Used);






// delete all data associated with each of the threads


delete <> TData;


}


// function used to assign a client request to a worker thread


BOOL WorkThePool(LPVOID p)


{


// we go in a queue manner to look for an available worker thread. 


//The loop goes on until it finds a thread and makes it busy...


while(WaitForSingleObject(TData.hFree,0)!=WAIT_OBJECT_0)


{


Next++;


if (Next>ThreadCount-1)


Next=0;


}


EnterCriticalSection(&gCS);


// signal the thread to start working... ResetEvent(TData.hFree);


LeaveCriticalSection(&gCS);


// get the data for the thread to work on


DWORD num=*((LPDWORD)p);


//...so set the data to be sent


TData.data=num;


// signal the thread to start working...


SetEvent(TData.hBusy);


// perform a context switch...


Sleep(0);


// ... and indicate success


return TRUE;


}


};





























































A client wishing to use the worker thread pool instantiates the CThreadPool class, specifying the number of the threads to be present in the thread pool and an array of function pointers to be used by each of the threads in the pool on a one-to-one basis, that is, thread 1 shall use pTFunc<0>, thread 2 shall use pTFunc<1>, and so on. Why, you ask, have I done this? After all, more often than not, only one kind of function is to be utilized by the threads from the worker thread pool, right? Right! But this has been done so that there is a complete control over the utilization of the worker threads. There might be a project in which threads from the same pool are required to work differently. This implementation takes care of such requirements as well, in addition to the utilization of a common function by the threads in the worker thread pool. Hence, the pool becomes generic in nature.

Kumar Gaurav Khanna runs

www.wintools.2fs.com

Advertisment