I am trying to create a boost thread in C++ that can be reused to run a variety of functions that can have different number and type of args.
Can this be done with C++11x variadics?
In my use case I do not need a queue (if the thread is busy then the method would simply fail), but if it is required to implement this "unify" functionality I would reluctantly do so.
I do not understand how to handle the unify with bind or lambda so that one thread can call different functions that each have their own and different quantities and types of args.
I have roughly the following in mind:
class WorkThread
{
public:
WorkThread()
{
// create thread and bind runner to thread
}
~WorkThread()
{
// tell runner to exit and wait and reap the thread
}
template<typename F,typename ... Arguments>
void doWork(F func, Arguments... args)
{
if already busy
return false;
// set indication to runner that there is new work
// here: how to pass f and args to runner?
}
private:
void runner()
{
while ( ! time to quit )
{
wait for work
// here: how to get f and args from doWork? do I really need a queue? could wait on a variadic signal maybe?
f(args);
}
}
boost::thread* m_thread;
};
class ThreadPool
{
public:
template<typename F, typename ... Arguments>
bool doWork(F func,Arguments... args)
{
const int i = findAvailableWorkThread();
m_thread[i].doWork(f,args);
}
private:
// a pool of work threads m_thread;
};
There should be lots of existing questions showing how to do this.
The canonical C++11 way to represent an arbitrary function object is std::function<void()>
, so you want a shared object of that type, called m_job
in the code below, which should be protected by a mutex, and you assign new jobs to it when it's not already set:
template<typename F,typename ... Arguments>
bool doWork(F func, Arguments&&... args)
{
std::lock_guard<std::mutex> l(m_job_mutex);
if (m_job)
return false;
m_job = std::bind(func, std::forward<Arguments>(args)...);
m_job_cond.notify_one();
return true;
}
This uses std::bind
to turn a function object and its arguments into a function object taking no arguments. The callable object returned by std::bind
stores a copy of func
and each argument, and when invoked it calls func(args...)
Then the worker just does:
void runner()
{
while ( ! time to quit )
{
std::function<void()> job;
{
std::unique_lock<std::mutex> l(m_job_mutex);
while (!m_job)
m_job_cond.wait(l);
swap(job, m_job);
}
job();
}
}
This code isn't thread-safe:
template<typename F, typename ... Arguments>
bool doWork(F func,Arguments... args)
{
const int i = findAvailableWorkThread();
m_thread[i].doWork(f,args);
}
After findAvailableWorkThread
returns, that thread could become busy, so the next line will fail. You should check for availability and pass the new job in a single operation, e.g.
template<typename F, typename ... Arguments>
bool doWork(F func,Arguments... args)
{
for (auto& t : m_thread)
if (t.doWork(f,args))
return true;
return false;
}