2016-05-18 22 views
1

Anthony Williamsの本の中の単純なスレッドプールの例を使用しようとしています"C++ Concurrency in Action"。私はここにコード(クラスthread_pool)を見つけました: Synchronizing tasks 私は別の質問があります。私は、次のシグネチャを持つキューにタスク(メンバ関数)を提出したいと思います:C++ 11スレッドプール - 入力パラメータを持つタスク

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

私はthread_poolクラスを変更する必要があります、またはどのように私は、いくつかの空に私の機能をパックするにはどうすればよいです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 
} 

}

そして最後に、どのように私は自分のコード内の関数を提出呼び出すことができますか?

ありがとうございます(unfortunatelly私はまだC++ 11のすべての機能を使用していますが、これはおそらく私がここで助けを必要としている理由ではありませんが、この質問に対する答えは何か:)と)。

答えて

3

タスクをキューに挿入するときに、パラメータを値にバインドする必要があります。つまり、thisの値と2つの関数パラメーターの値を格納する関数用のラッパーを作成する必要があります。これを行うにはさまざまな方法があります。ラムダ関数または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)...)); 
} 

メンバ関数の場合、メンバ関数とオブジェクトの特別なオーバーロードを作成すると便利です。

+0

バインダーを使用しているときに、参照の上にどのような制御があるのでしょうか...バインダーは常に元のタイプのコピーを保存していますか? )?その場合、重複のパフォーマンスがわずかに低下しますが、これは素晴らしい解決策です。 –

+0

@WernerErasmus 'std :: bind'とラムダキャプチャは引数をコピーします。参照を格納する場合は、 '[&a、&b](){}'を参照するか、 'std :: bind(f、std :: ref(a)、std :: ref(b)) 'を返します。 – Jens

+0

コピーは安全なオプションですが、これは時期尚早の最適化であるかもしれませんが、バインドを使用しない場合もあります(私が行ったように)。つまり、このタイプのコードは「一般的に」使用されるため、予期せぬ(大きな引数など)ために最適化が必要になる可能性があります。 –

0

これまでに(非常に)似たようなことを書いています。私はここにコードを掲載し、あなたは一見することができます。 GenCmdは関数ラッパーです。キューはこのように見え、Impl(コード省略)で使用/定義されています。これには必要な作業が含まれているので、GenCmdの実装だけを見る必要があります。

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

私はキューに多型であることをのstd ::機能<>を包みました。 std_utilityにはmake_index_sequenceが含まれています。これはタプル(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で& &)を受信した場合、記憶されているタイプは、従って、移動を引き起こし、元の型にキャストし、空の(一度だけ呼び出される)該当するコマンド引数をレンダリングしています(それは、少なくとも、テストされていません...)

関連する問題