I am currently tasked with writing a generic thread pool, having multiple worker threads and one scheduling thread in c++. To support running any kind of function within one pool I used variadic functions and parameter packs, in which I have little experience.
My thread pool and worker class looks like this
template<typename R, typename ...A>
class Worker {
public:
// starts thread that runs during lifetime of worker object
Worker() : thread_([this] {
while (!stopped) {
// run task that worker has been set to and remove it thereafter
if (task_ != NULL) {
idle = false;
task_(std::get<A>(args_)...);
task_ = NULL;
}
idle = true;
}
}) { }
~Worker() {
stop();
}
void stop() {
stopped = true;
thread_.join();
}
bool idling() {
return idle;
}
void set_work(std::function<R(A...)> task, std::tuple<A...> args) {
task_ = task;
args_ = args;
}
private:
std::thread thread_;
std::function<R(A...)> task_;
std::tuple<A...> args_;
bool idle = false;
bool stopped = false;
};
template<typename R, typename ...A>
class ThreadPool {
public:
// pool runs scheduling thread which assigns queued tasks to idling workers
ThreadPool(size_t num_workers) : workers(num_workers), num_workers_(num_workers), runner([this, num_workers] {
while(!stopped) {
for (size_t i = 0; i < num_workers; i++) {
if (workers[i].idling() && !q.empty()) {
workers[i].set_work(q.front().first, q.front().second);
q.pop();
}
}
}
}) { }
void add_task(std::function<R(A...)> task, A... args) {
q.push({task, std::make_tuple(args...)});
}
size_t tasks_left() {
return q.size();
}
size_t workers_idling() {
size_t n = 0;
for (size_t i = 0; i < num_workers_; i++) {
if (workers[i].idling()) n++;
}
return n;
}
void stop() {
for (size_t i = 0; i < num_workers_; i++) {
workers[i].stop();
}
stopped = true;
runner.join();
}
private:
std::vector<Worker<R, A...>> workers;
std::queue<std::pair<std::function<R(A...)>, std::tuple<A...>>> q;
std::thread runner;
bool stopped = false;
size_t num_workers_;
};
The first hurdle I encountered was that I was not able to use references as variadic types, so I used the whole object. But any class not specifying a default constructor, throws the following error https://pastebin.com/ye6enTD3. Accordingly for any other class which does, the member variables are not consistently the same as the object I passed to the worker.
I would appreciate your help on this topic.
<typename fn_t> auto schedule(fn_t fn) -> std::future<decltype(fn())>as entry point.std::atomic).std::refas a reference as a variadic type? Orstd::reference_wrapper? (I don't normally use either of those, so I'm not sure which is applicable for your use case.)