I want to create a thread pool for experimental purposes (and for the fun factor). It should be able to process a wide variety of tasks (so I can possibly use it in later projec
I happen to have an implementation which does exactly that. My way of doing things is to wrap the std::packaged_task
objects in a struct which abstracts away the return type. The method which submits a task into the thread pool returns a future on the result.
This kind of works, but due to the memory allocations required for each task it is not suitable for tasks which are very short and very frequent (I tried to use it to parallelize chunks of a fluid simulation and the overhead was way too high, in the order of several milliseconds for 324 tasks).
The key part is this structure:
struct abstract_packaged_task
{
template
abstract_packaged_task(std::packaged_task &&task):
m_task((void*)(new std::packaged_task(std::move(task)))),
m_call_exec([](abstract_packaged_task *instance)mutable{
(*(std::packaged_task*)instance->m_task)();
}),
m_call_delete([](abstract_packaged_task *instance)mutable{
delete (std::packaged_task*)(instance->m_task);
})
{
}
abstract_packaged_task(abstract_packaged_task &&other);
~abstract_packaged_task();
void operator()();
void *m_task;
std::function m_call_exec;
std::function m_call_delete;
};
As you can see, it hides away the type dependencies by using lambdas with std::function
and a void*
. If you know the maximum size of all possibly occuring std::packaged_task
objects (I have not checked whether the size has a dependency on R
at all), you could try to further optimize this by removing the memory allocation.
The submission method into the thread pool then does this:
template
std::future submit_task(std::packaged_task &&task)
{
assert(m_workers.size() > 0);
std::future result = task.get_future();
{
std::unique_lock lock(m_queue_mutex);
m_task_queue.emplace_back(std::move(task));
}
m_queue_wakeup.notify_one();
return result;
}
where m_task_queue
is an std::deque
of abstract_packaged_task
structs. m_queue_wakeup
is a std::condition_variable
to wake a worker thread up to pick up the task. The worker threads implementation is as simple as:
void ThreadPool::worker_impl()
{
std::unique_lock lock(m_queue_mutex, std::defer_lock);
while (!m_terminated) {
lock.lock();
while (m_task_queue.empty()) {
m_queue_wakeup.wait(lock);
if (m_terminated) {
return;
}
}
abstract_packaged_task task(std::move(m_task_queue.front()));
m_task_queue.pop_front();
lock.unlock();
task();
}
}
You can take a look at the full source code and the corresponding header on my github.