Put simply, you create a fixed number of "worker" threads
once, and you keep them running
continuously – instead of creating short-lived threads for each task.
Each "worker" thread executes an
infinite loop that looks like this:
Code:
void thread_main(void)
{
while(!g_isTerminating)
{
Task nextTask = g_queue.take();
nextTask.execute();
}
}
In the above code, it is assumed that the queue
g_queue is global, i.e.
shared between all worker threads (as well as with the rest of the application), and that it is synchronized, i.e.
thread-safe. Also, it is assumed that
take() returns the "next" item from the queue and removes that item from the queue at the same time – both in a single "atomic" operation. Last but not least, it is assumed that
take() will
block, while there is
no item left in the queue.
Now, in order to have some task executed by the thread pool, you simply push the task object onto the queue, e.g. from the "main" thread, and that's it:
Code:
void main(void)
{
/* .... */
for(int i = 0; i < numberOfTasks; ++i)
{
Task taskToBeExecuted = new Task(/*task-specific parameters go here*/);
g_queue.push(taskToBeExecuted);
}
/* .... */
}
This is actually an example of the well-known
producer consumer pattern. Here the "main" thread is the
producer, i.e. it produces tasks, and the "worker threads" are
consumers, i.e. they consume tasks.
BTW: Because most "standard" queue implementations, such as C++'s
std::queue, are
not thread-safe, you will usually need to use a
mutex in order to synchronize things. Also, in order to implement the case that the queue is empty – you want your threads to
sleep in that case, instead of doing a highly inefficient "busy waiting" – you will probably have to use
condition variables. Well, either that, or forget everything I said and just use C++11's
std::async()