Reputation: 479
I have a thread pool that should accept any std::packaged_task and give a future.
template<typename RetType>
template<typename Args...>
std::future<RetType> submitWork(std::packaged_task<RetType(Args...)>&& callableWork>);
As you can see the packaged_task is templated. Now my thread pool uses a lockless queue as a member of the class
class ThreadPool
{
public:
private:
llQueue<boost::variant<???>> workQueue;
}
I want the work queue to be a variant of the types that submitWork gets called by. Ex: this code
bool runByPool(int var)
{
//do stuff
}
int runAlso(char c)
{
//do other stuff
}
ThreadPool pool; // 4 worker threads
pool.submitWork<bool(int)>(std::bind(runByPool, 1));
pool.submitWork<int<c>>(std::bind(runAlso, 'a'));
Gives the following type to workQueue at compilation:
llQueue<boost::variant<std::packaged_task<bool(int)>,
std::packaged_task<int(c)>
>
>
How do i make the member of the class use the types of the templated submitWork? I want to force the llQueue to only hold std::packaged_task's and i used a variant so that i can avoid heap allocation since this need to be highly, highly performant.
I would like to avoid heap allocations and i need the same pool to be able to execute any work with any return type or parameter type
Upvotes: 0
Views: 175
Reputation: 275385
Most the code you posted does not compile.
pool.submitWork<bool(int)>(std::bind(runByPool, 1));
the signature of std::bind(runByPool, 1)
is bool()
not bool(int)
. The same error is in your other example, well, ignoring the other syntax errors there.
std::future<RetType> submitWork(std::packaged_task<RetType&&(Args&&...)&& callableWork>);
this signature is insanity. It should be;
std::future<RetType> submitWork(std::packaged_task<RetType(Args...)> callableWork);
next, it makes almost no sense to take work that still needs args of a non-uniform type. Which is reflected in your examples.
In fact, taking a packaged task here is pointless.
std::future<RetType> submitWork(std::function<RetType()> callableWork>);
makes more sense. You take an operation returning a T, and return a future T.
llQueue<boost::variant<???>> workQueue;
there is no need for a variant here. You want a queue of tasks you can run. Their return type should already be routed elsewhere, and their arguments are already bound.
llQueue<std::function<void()>> workQueue;
now there remains a technical issue. std::function<void()>
requires that it be copyable; bit the easy way to wire the callableWork to a future leaves you with a non-copyable packaged task.
There are a few ways around this. The first is to shove the packaged task into a shared ptr then store that in a function. The second is noting that packaged_task<T()>
is a move only callable with signature void()
, which can be stored in a packaged_task<void()>
.
So we come full circle.
struct ThreadPool {
template<class F, class R=std::result_of_t<F&()>>
std::future<R> submitWork(F f){
auto task=std::packaged_task<R()>(std::move(f));
auto r=task.get_future();
workQueue.push_back(std::packaged_task<void()>(std::move(task)));
return r;
}
std::vector<std::packaged_task<void()>> workQueue;
// or:
//llQueue<std::packaged_task<void()>> workQueue;
// with changes to how things are enqueued
};
and as a bonus it deduces the return type for you. Live example.
I believe I have seen at least one C++ compiler that screwed up and made packaged tasks require copyable contents. So the shared ptr containing function may be your backup plan.
Upvotes: 1