在C++ 11中实现一个简单的通用线程池

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()>并且调用的函数返回一个整数,或者任何东西,那么这就是未定义的行为.

Yak*_*ont 6

所以困难的部分是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使用小缓冲区优化来改进容器.