Introduction
One of my applications will take periodic data backup to multiple destinations (CD, USB, NETWORK SHARE). When I wrote a single threaded application for this, it took more than two hours to complete a single backup. It was a challenge for me and the research to improve the performance ended up in multi-threading. The result was amazing, the backup completed within 30 minutes.
The beginning of the development, the backup period was one day, later I forced to increase the frequency to one hour. Now I noticed that the application has an overhead of thread creation and destruction due to small frequency. How to solve it? Why threads can't be re-used? These thoughts ended up in a thread pool.
Whether Thread Pool is Needed for You?
- Do you have multiple requests to be processed repeatedly and parallel?
- Each request can be processed independently?
- Do you have waiting IO/File operations?
If you have answered yes, then you can go for thread pool. The application you are going to design should have low coupling for easy implementation of thread pool.
Background
In the career of a programmer, he will be forced to create at least a single thread pool. In my case also, the same happened. I forced to create a thread pool in C++. As usual , I logged into Google for downloading the same, but this time the result was negative. Even though there are lots of thread pool applications, I could not find a suitable one for me. It brings me here to write this tip.
Why We Need a Thread Pool?
Normally, most of the IO operations (file, disk, etc.) will take a long time to complete; so in a single threaded application, system resources are in waiting state during IO operations. This waiting time of system resources(memory, processor, etc.) can be effectively utilized by implementing multiple threads. So while IO thread is performing IO operation, non thread can utilize memory and processor effectively.
A thread pool will be effective in the following situations:
- An application needs to avoid thread creation and destruction time in its scenarios.
- An application that is parallel and can dispatch a large number of small work items asynchronously.
- An application that creates and destroys a large number of threads that each run for a short time. Using the thread pool can reduce the complexity of thread management and the overhead involved in thread creation and destruction.
- An application that processes independent work items in the background and in parallel.
Thread Pool Design
The thread pool will create specified number of threads and wait for request. Once a request is posted to thread pool, one thread will be active and start execution. After the completion of the request processing, the thread will go back to waiting state and wait for next request to be queued. When user requested destroy, all threads will be exited from the thread pool.
The class diagram is shown below:
ThreadPool
This class will create, manage and
destruct thread pool. The user (your application) of the ThreadPool
should create an object
of ThreadPool
class in their application. While creating thread pool,
user can specify the thread count. This thread pool will support maximum of
64 threads even though it can reduce the count to minimum for avoiding the overhead of
thread switching and system resources usage.
AbstractRequest
It stands for a request in thread pool. The client application should derive this class and write the code to be executed inside the thread in Execute()
function. The user should ensure thread safety of this function by using critical section object provided. Also cancel handling can be done by using IsAborted()
function meaning fully. Then post the derived abstract request instance to thread pool for processing.
Canceling can be done by calling Abort()
function.
Logger
The thread pool has a facility for error and
information logging. The default log location is debugger window. This can be
overridden by creating a user-defined class, which is derived from Logger
class. Then override LogError()
and LogInfo()
with needed logging mechanism.
Give the instance of logger class to thread pool during the creation.
How can thread pool be in your application?
The user application will hold an instance of ThreadPool
class. While receiving create request, it will create specified number of threads and each
thread will wait for request queuing. Once a request received, then one thread
will come out the wait state and start processing the request. After completion
of the request processing, the thread will go back to the waiting state.
The user can abort processing of request by calling Abort()
method of AbstractRequest
class at any time. The thread pool will not delete request after completion of
processing.
Using the Code
- Create()
bool Create( const unsigned short usThreadCount_i, Logger* pLogger_io = NULL );
This function will create a specified number of threads in
waiting state. If the Logger is specified, then it will be used for error logging. This
function will return false
in case of failure. The maximum number of threads
is limited to 64.
- Destroy()
bool Destroy();
This function will abort all requests and destroy the thread
pool. It will return false
in case of failure.
- PostRequest()
bool PostRequest( AbstractRequest* pRequest_io );
This function will post specified
request to thread pool for processing and it will return false
in case of
failure.
Dependency
ThreadPool.h includes windows.h, list.h (STL) and string.h (STL)
How the thread pool can be used in your application?
- Include ThreadPool.h and ThreadPool.cpp
in your application.
- The thread pool will log error
and information in debugger window and this behavior can be overridden if
needed. This default behavior can change by deriving a class from
Logger
, and derive LogError()
and LogInfo()
functions. - Create thread pool by calling
Create()
function. Provide the Logger
instance if needed. - Create a class derived from
AbtractRequest
and derive Execute()
function which acts as thread procedure. - Post the instance of the class
which derived from
AbtractRequest
to thread pool for processing using PostRequest()
function. User can post requests without any limits,
but active request count will be same as thread count. - Once processing is completed, you can destroy the
thread pool using
Destroy()
function.
ThreadPoolDemo
test application is available for familiarizing the usage of ThreadPool
class.
Points of Interest
This
thread pool is implemented for Windows operating system and it can be ported to
Linux or Apple platform.