Here's a rather hastily made thread pool to suit my requirements and even for my future projects. It is working fine and executing tasks as intended but I want to improve more and I am not sure if it's the proper way to design a thread-pool as I am a self taught enthusiast, and rather weak as a language lawyer.
Tasks are stored in the queue. They are executed in order only when an idle thread is available from the pool. New tasks can be pushed into the queue. To initialize the pool, one has to specify the total number of threads and a bool
variable which will be used to terminate the pool and threads gracefully.
#include "stdafx.h"
#include <iostream>
#include <memory>
#include <thread>
#include <vector>
#include <queue>
#include <chrono>
#include <mutex>
#include <condition_variable>
using namespace std;
typedef function<void(void)> task_t;
class thread_t
{
public:
thread_t(int id, bool& running, condition_variable& cv)
:id_(id)
, running_(running)
, idle_notify_cv_(cv)
{
idle_ = true;
thread_ = new thread([=]() { run(); });
}
~thread_t()
{
notify();
cout << id_ << " stopping \n";
thread_->join();
}
void push(task_t task)
{
task_ = task;
idle_ = false;
cv_.notify_one();
}
void notify()
{
cv_.notify_all();
}
bool is_idle() const
{
return idle_;
}
int get_id() const
{
return id_;
}
private:
void run()
{
cout << id_ << " starting \n";
while (running_)
{
unique_lock<mutex> lock(mu_);
cv_.wait(lock, [=]() { return idle_ == false || !running_; });
if (!running_) return;
task_();
cout << id_ << " :work done \n";
idle_ = true;
idle_notify_cv_.notify_all();
}
}
private:
condition_variable& idle_notify_cv_;
mutex mu_;
condition_variable cv_;
task_t task_;
thread* thread_;
bool idle_;
int id_;
bool& running_;
};
class pool
{
public:
pool(int n, bool& running)
:nthreads_(n)
,running_(running)
{
if (n > std::thread::hardware_concurrency()) nthreads_ = n = std::thread::hardware_concurrency()-1;
for (int i = 0; i < n; i++)
{
threads_.push_back(make_unique<thread_t >(i, running_, idle_notify_cv_));
}
pool_thread_ = new thread([=]() { run(); });
}
void push(task_t task)
{
unique_lock<mutex> lock(write_queue_mu_);
tasks_.push(task);
idle_notify_cv_.notify_one();
}
int get_idle()
{
for (int i = 0; i < nthreads_; i++)
{
if (threads_[i]->is_idle())
{
return i;
}
}
return -1;
}
void run()
{
cout << " pool thread started \n " ;
while (running_)
{
int idle;
if (!tasks_.empty() && (idle = get_idle()) != -1)
{
unique_lock<mutex> lock(write_queue_mu_);
idle_notify_cv_.wait(lock, [=]() { return idle != -1 || !running_; });
if (!running_) return;
auto task = tasks_.front();
tasks_.pop();
lock.unlock();
cout << " thread# " << threads_[idle]->get_id() << " assigned a task \n";
threads_[idle]->push(task);
}
}
}
~pool()
{
pool_thread_->join();
cout << " thread pool destroyed \n ";
}
private:
mutex write_queue_mu_;
queue<task_t> tasks_;
vector<unique_ptr<thread_t>> threads_;
int nthreads_;
bool& running_;
condition_variable idle_notify_cv_;
thread* pool_thread_;
};
int main()
{
bool running = true;
pool pool1(2, running);
task_t task1 = []()
{
this_thread::sleep_for(chrono::seconds(2s));
cout << " Task 1 executed \n";
};
task_t task2 = []()
{
this_thread::sleep_for(chrono::seconds(1s));
cout << " Task 2 executed \n";
};
task_t task3= []()
{
this_thread::sleep_for(chrono::seconds(2s));
cout << " Task 3 executed \n";
};
task_t task4 = []()
{
this_thread::sleep_for(chrono::seconds(1s));
cout << " Task 4 executed \n";
};
pool1.push(task1);
pool1.push(task2);
pool1.push(task3);
pool1.push(task4);
this_thread::sleep_for(chrono::seconds(5s));
running = false;
return 0;
}