C ++ 11线程池 - 具有输入参数的任务

时间:2016-05-18 06:58:58

标签: multithreading c++11 threadpool

我正在尝试使用Anthony Williams “C ++ Concurrency in Action”一书中的简单线程池示例。我甚至在其中一个帖子中找到了这里的代码(类 thread_pool ): Synchronizing tasks 但我有一个不同的问题。我想使用以下签名向队列提交任务(成员函数):

class A;
class B;
bool MyClass::Func(A*, B*); 

我如何更改 thread_pool 类,或者如何在一些 void F()中打包我的函数,假设它被用作任务在这个例子中? 这是我课程中最相关的部分(详情请见上面的链接):

class thread_pool 
{
  thread_safe_queue<std::function<void()> work_queue; // bool MyClass::Func(a,b) ??

  void worker_thread() {
   while(!done) {         
    std::function<void()> task;
    if(work_queue.try_pop(task)) {
     task();  // how should my function MyClass::Func(a,b) be called here?                    
    }
    else {
     std::this_thread::yield();
    }
   }
  }

  // -- Submit a task to the thread pool
  template <typename FunctionType>
  void submit(FunctionType f) {
  work_queue.push(std::function<void()>(f)); // how should bool MyClassFunc(A*, B*) be submitted here
 }

}

最后,如何在我的代码中调用submit函数?

非常感谢你的帮助(不幸的是,我在使用所有C ++ 11功能方面还不是很有经验,这可能也是我在这里需要帮助的原因,但这个问题的答案将是开始的。 :))。

2 个答案:

答案 0 :(得分:3)

将任务插入队列时,必须将参数绑定到值。这意味着您必须为函数创建一个包装器,用于存储this的值和两个函数参数的值。有很多方法可以做到这一点,例如: lambda函数或std::bind

work_queue.push_back( [obj, a, b]() {obj->Func(a,b)} );
work_queue.push_back( std::bind(&MyClass::Func, obj, a, b) );

您的提交功能必须采用这些参数并创建绑定,例如

template<typename F, typename... Args>
void submit(F f, Args&&... args) {
    work_queue.push_back( std::bind(f, std::forward<Args>(args)...) );
}

对于成员函数,可以方便地为成员函数和对象创建特殊的重载。

答案 1 :(得分:0)

我之前写的东西(非常)与此类似。我将在此处发布代码,您可以查看一下。 GenCmd是函数包装器。队列看起来像这样,并在Impl中使用/定义(代码省略)。您只需要查看GenCmd的实现,因为它包含必要的工作。

ConcurrentQueue<std::unique_ptr<Cmd>> cqueue_;

我已经包装了std :: function&lt;&gt;在队列中是多态的。 std_utility包含make_index_sequence,用于从元组中提取值(如果这不是std库的一部分,则google make_index_sequence在某处找到实现)。

#include <functional>
#include <memory>
#include <iostream>
#include <utility>
#include <boost/noncopyable.hpp>

class CmdExecutor : public boost::noncopyable
{
  public:
    CmdExecutor(std::ostream& errorOutputStream);
    ~CmdExecutor();

    template <class Receiver, class ... FArgs, class ... CArgs >
      void process(Receiver& receiver, void (Receiver::*f)(FArgs...), CArgs&&... args)
    {
      process(std::unique_ptr<Cmd>(new GenCmd<void(Receiver,FArgs...)>(f, receiver, std::forward<CArgs>(args)...)));
    }

  private:
    class Cmd
    {
      public:
        virtual void execute() = 0;
        virtual ~Cmd(){}
    };

    template <class T> class GenCmd;

    template <class Receiver, class ... Args>
    class GenCmd<void(Receiver, Args...)> : public Cmd
    {
      public:
        template <class FuncT, class ... CArgs>
        GenCmd(FuncT&& f, Receiver& receiver, CArgs&&... args)
          : call_(std::move(f)),
            receiver_(receiver),
            args_(args...)
        {
        }
        //We must convert references to values...

        virtual void execute()
        {
          executeImpl(std::make_index_sequence<sizeof...(Args)>{});
        }

      private:
        template <std::size_t ... Is>
        void executeImpl(std::index_sequence<Is...>)
        {
          // We cast the values in the tuple to the original type (of Args...)
          call_(receiver_, static_cast<Args>(std::get<Is>(args_))...);
        }

        std::function<void(Receiver&, Args...)> call_;
        Receiver& receiver_;
        // NOTE:
        // References converted to values for safety sake, as they are likely
        // to not be around when this is executed in other context.
        std::tuple<typename std::remove_reference<Args>::type...> args_;
    };

    void process(std::unique_ptr<Cmd> command);

    class Impl;
    Impl* pimpl_;
};

它的基本用法如下:

...
CmdExecutor context_;
...

void MyClass::myFunction()
{
  ArgX x;
  ArgY y;

  context_.process(*this, &MyClass::someFunction, x, y);
}

您可以从中看到,该进程执行成员函数类型的包装并将其转换为基础类型以便存储在队列中。这允许多种参数类型。我选择使用运行时多态来存储函数类型,因此也就是GenCmd的派生。

注意:如果调用的函数接收到右值(Arg&amp;&amp;),则存储的类型将转换为原始类型,从而导致移动,并将适用的命令参数(只能调用一次)呈现为空(这是意图,至少 - 未经测试......)