kri*_*pet 5 c++ multithreading packaged-task c++11 stdthread
我想创建一个用于实验目的的线程池(以及有趣的因素).它应该能够处理各种各样的任务(所以我可以在以后的项目中使用它).
在我的线程池类中,我将需要某种任务队列.由于标准库std::packaged_task从C++ 11标准开始提供,我的队列看起来像std::deque<std::packaged_task<?()> > task_queue,所以客户端可以std::packaged_task通过某种公共接口函数将s 推入队列(然后池中的一个线程将通知一个条件变量来执行它,等等.
我的问题与std::packaged_task<?()>deque中s 的模板参数有关.
函数签名?()应该能够处理任何类型/数量的参数,因为客户端可以执行以下操作:
std::packaged_task<int()> t(std::bind(factorial, 342));
thread_pool.add_task(t);
所以我不必处理参数的类型/数量.
但是回报价值应该是多少?(因此问号)
如果我将整个线程池类作为模板类,它的一个实例将只能处理具有特定签名的任务(如std::packaged_task<int()>).
我希望一个线程池对象能够处理任何类型的任务.
如果我使用std::packaged_task<void()>并且调用的函数返回一个整数,或者任何东西,那么这就是未定义的行为.
所以困难的部分是packaged_task<R()>仅移动,否则你可以把它扔进一个std::function<void()>,并在你的线程中运行它们.
有几种方法可以解决这个问题.
首先,可笑的是,用一个packaged_task<void()>来存储一个packaged_task<R()>.我建议不要这样做,但确实有效.;)(operator()on 的签名是packaged_task<R()>什么packaged_task<void()>?传递给的对象所需的签名是什么?)
其次,包装你packaged_task<R()>的shared_ptr,捕获在拉姆达与签名void(),存储在std::function<void()>和完成.这有开销成本,但可能低于第一个解决方案.
最后,编写自己的仅移动函数包装器.签名void()很简短:
struct task {
template<class F,
class dF=std::decay_t<F>,
class=decltype( std::declval<dF&>()() )
>
task( F&& f ):
ptr(
new dF(std::forward<F>(f)),
[](void* ptr){ delete static_cast<dF*>(ptr); }
),
invoke([](void*ptr){
(*static_cast<dF*>(ptr))();
})
{}
void operator()()const{
invoke( ptr.get() );
}
task(task&&)=default;
task&operator=(task&&)=default;
task()=default;
~task()=default;
explicit operator bool()const{return static_cast<bool>(ptr);}
private:
std::unique_ptr<void, void(*)(void*)> ptr;
void(*invoke)(void*) = nullptr;
};
Run Code Online (Sandbox Code Playgroud)
而且简单.以上可以存储packaged_task<R()>任何类型R,并在以后调用它们.
这具有相对最小的开销 - 它应该比std::function我看到的实现更便宜- 除了它不执行SBO(小缓冲区优化),其中它在内部而不是在堆上存储小函数对象.
如果需要,可以unique_ptr<> ptr使用小缓冲区优化来改进容器.