Introduction
Sometimes, we need an extra thread to do a task for us in parallel or to just unblock main thread. For better performance, we would not like to create a new thread every time. Also, we need little more control over the thread. In this article, we will implement a thread with the below abilities.
- You need not define a thread procedure, rather you can submit any function using lambda
- Implemented purely in C++11, compatible with any OS
- Submit a task to be executed asynchronously
- Submit a task to be executed synchronously (but on worker thread)
- Automatically wait for task to complete
- Implementation uses C++ 11 extensively (thread, lambda, condition variables)
Using the Code
The below sample code lists usages of worker thread:
#include <iostream>
#include <chrono>
#include <thread>
#include "workerthread.h"
int main()
{
std::cout << "Hi, Welcome to demo of worker thread" << std::endl;
{
WorkerThread thread;
WorkerThread thread2;
thread.doSync([]{ std::cout << "First - blocking call" << std::endl; });
for (int i = 1; i < 100; i++)
{
auto& t = i % 2 == 0 ? thread : thread2;
if (i == 10) {
thread.doSync([]{
std::cout << "Second - blocking call" << std::endl; });
}
t.doAsync([i]
{
std::this_thread::sleep_for(std::chrono::duration<int, std::milli>(200));
std::cout << (i % 2 == 0 ? "thread-" : "thread2-")
<< "iteration number: " << i << std::endl;
});
}
thread.doSync([]{ std::cout << "Last - blocking call"; });
} std::cout << "This must be last line\n";
}
Implementation of worker thread:
- Use C++ function construct to store submitted task
- Finish the present queue and wait for signal for new work
void WorkerThread::startThread()
{
std::unique_lock<std::mutex> l(mutex);
do
{
while (isRunning && tasks.empty())
itemInQueue.wait(l);
while (!tasks.empty())
{
auto size = tasks.size();
printf("Number of pending task are %d\n", size);
const std::function<void()> t = tasks.front();
tasks.pop_front();
l.unlock();
t();
l.lock();
}
itemInQueue.notify_all();
} while (isRunning);
itemInQueue.notify_all();
}
An asynchronous task is just queued and calling thread not blocked:
void WorkerThread::doAsync(const std::function<void()>& t)
{
std::lock_guard<std::mutex> _(mutex);
tasks.push_back(t);
itemInQueue.notify_one();
}
Synchronous task is little trickier, we need to block calling thread until all tasks queued before the submitted task and submitted task completes.
void WorkerThread::doSync(const std::function<void()>& t)
{
std::condition_variable event;
bool finished = false;
std::unique_lock<std::mutex> l(mutex);
auto lambda = [this, &t, &finished, &event]
{
t();
std::lock_guard<std::mutex> l(mutex);
finished = true;
event.notify_one();
};
tasks.push_back(lambda);
itemInQueue.notify_one();
while (!finished)
event.wait(l);
}
With minor changes, this class can be used to take priority base task.