Often there is a need to run several tasks concurrently in such a way that there is optimum use of the operating system resources. If each task were to be implemented as a new thread, then there would be a number of inefficiencies:
The ThreadPool solves above problems. It implements a pool of threads to which tasks can be submitted for execution. The pool starts with a number of pre-allocated threads (Minimum Threads). When load increases, new threads are started until a Maximum limit is reached. When load decreases, the pool retains a number of idle threads (Maximum Spare threads) and terminates the excess.
When a task is submitted to the pool, the first available thread executes it. If all threads are busy, the pool tries to create more threads. If no more threads can be created because the maximum number of threads has been already created, then the task must wait until one of the busy threads becomes available to process it.
There are two operations supported by a ThreadPool.
RunTask - This allows a Task to be submitted for execution. The first available thread executes the task. If there are no available threads, then the ThreadPool attempts to create a new thread. Creation of new thread is possible only if the number of threads in the pool is below the maximum limit. If a thread cannot be created, then the caller is blocked until a thread becomes free to execute the task.
QueueTask - This is similar to RunTask except that the caller does not have to wait for a thread to be allocated for the task. The task is added to a queue, and control returns to the caller immediately.
The DM1 ThreadPool implementation is based upon the ThreadPool implementation in Apache Software Foundation's Tomcat Jakarta Connectors project. There are a few differences between the DM1 implementation and the Jakarta implementation:
1. In Java, certain operations on primitive types are guaranteed to be thread safe. C++ offers no such guarantee - hence there is more locking.
2. The Java code does not have support for queuing tasks. If all threads are busy, the Pool blocks the caller until a thread became available to run the task. The DM1 version allows the caller to queue a task and not wait for a thread to become available. A separate thread, called the QueueHandler, monitors the task queue, and runs any tasks in the queue.
The ThreadPool consists of a pool of WorkerThreads. A WorkerThread is responsible for executing a task. When a task is submitted, the first available WorkerThread is allocated the task for execution. When the WorkerThread finishes executing the task, it returns itself to the pool.
If a WorkerThread is not available to execute a task, the pool attempts to start a new WorkerThread (actually, for reasons of efficiency, more than one WorkerThread is started). However, it can do so only if the number of threads in the pool is below the maximum limit. If allocation of a new thread fails due to above reason, then the task cannot be executed until a WorkerThread becomes available.
The following parameters within the ThreadPool can be configured during construction of the ThreadPool:
MinimumThreads - This defines the minimum number of WorkerThreads that will be kept running at all times.
MinimumSpareThreads - The pool will try to maintain at least these many spare threads.
MaximumSpareThreads - The pool will tolerate at least these many spare threads when load decreases, before starting to terminate excess threads.
MaximumThreads - The pool will never allocate more than these many threads.
The ThreadPool has a MonitorThread running at all times. This thread wakes up periodically and instructs excess WorkerThreads to terminate. The number of threads that are in excess is calculated on using the following formula:
Excess Threads = Total Number of Threads - Number of Active Threads - Maximum Spare Threads.
The ThreadPool also has a QueueHandlerThread running at all times. This thread is responsible for executing tasks that have been put on the task queue. The task queue is a linked list of tasks, to which a task is added when the caller performs a QueueTask operation.
Tasks must be derived from the Task class. The derived class must implement the run method. The ThreadPool expects tasks to be allocated on the heap, and deletes them once they have been executed. Hence, the programmer must never delete a task.
namespace dm1 {
/**
* Base class for all tasks.
*/
class Task {
public:
virtual ~Task() ;
/**
* Derived classes need to implement this method.
*/
virtual void run() = 0;
};
/**
* The ThreadPool class implements a pool of Worker threads. Tasks can be
* queued for execution by one of the Worker threads. The pool is dynamic - it will
* start new worker threads if necessary, or terminate existing worker threads when
* no longer required.
*/
class ThreadPool {
public:
/**
* Creates a thread pool.
*/
explicit ThreadPool(
int maxThreads = MAX_THREADS,
int minSpareThreads = MIN_SPARE_THREADS,
int maxSpareThreads = MAX_SPARE_THREADS,
int monitorTimeout = WORK_WAIT_TIMEOUT,
int queueHandlerTimeout = QUEUE_HANDLER_TIMEOUT);
/**
* Starts the ThreadPool.
*/
void start();
/**
* Executes the task provided.
* Caller blocks until a thread is available to run the task.
* Task must be allocated on the heap.
* The pool will delete the task when it is
* completed.
*/
void runTask(Task *toRun);
/**
* Asynchronously runs the task submitted.
* Control returns to the caller immediately.
* Task must be allocated on the heap.
* The pool will delete the task when it is
* completed.
*/
void queueTask(Task *toRun);
/**
* Shuts down the pool.
*/
void shutdown();
static void setDebug(int level) ;
};
}