2012-01-30 3 views
5

オブジェクトからの状態変化に関する通知が、よく定義された順序でスロットに到着することを確認するために、boost :: signals2シグナルのマルチスレッド呼び出しをシリアル化したいと思います。boost :: signals2シグナルの呼び出しをシリアル化する既存の方法はありますか?

背景

私は、マルチスレッドプログラムで内部状態を持つオブジェクトを持っています。内部状態の一部は、プログラムの他の部分のために興味深いものです、そしてオブジェクトは、ブーストを使用して状態の変化を公開::これと同様の信号S2信号、:

class ObjectWithState { 
public: 
    enum State { 
     STATE_A, 
     STATE_B, 
     STATE_C, 
    }; 

    void OnEvent() { 
     State newState; 
     { 
      boost::lock_guard<boost::mutex> lock(m_Mutex); 
      // Process event and change state 
      m_State = ...; 
      newState = m_State; 
     } 
     m_OnStateChanged(newState); 
    } 

    // method to allow external objects to connect to the signal etc 
private: 
    boost::signals2::signal<void (State) > m_OnStateChanged; 
    boost::mutex m_Mutex; 
    State m_State; 
}; 

問題

あった場合OnEventハンドラの複数の同時呼び出しであるため、実際に行われた変更よりも別の順序で状態の変更が通知される可能性があります。状態そのものは上記のようなmutexによって保護されているので、の実際の状態は明確に定義されています。ただし、デッドロックの原因となる可能性があるため、ミューテックスはシグナルの呼び出しを介して保持することはできません。これは、信号の実際の呼び出しはどのような順序でも起こりうるが、ステートの変更が実際に行われたのと同じ順序で呼び出される必要があることを意味する。

この問題を処理する1つの方法は、信号から状態を削除し、状態が変更されたことをリスナーに通知することです。次に、彼らはそのオブジェクトの状態を問い合わせることができ、信号が発射されたときにオブジェクトが持っていた状態を得ることができる。またはそれ以降の状態。私のシナリオでは、リスナーはすべて状態の変更について通知する必要があるので、この方法はここでは機能しません。私は上記のコードを試していないので、それはバグやコンパイルエラーが散らばっされるかもしれないが、どのようなI推定することが可能でなければなりません

class ObjectWithState { 
public: 
    enum State { 
     STATE_A, 
     STATE_B, 
     STATE_C, 
    }; 

    void OnEvent() { 
     State newState; 
     boost::unique_future<void> waitForPrevious; 
     boost::shared_ptr<boost::promise<void> > releaseNext; 
     { 
      boost::lock_guard<boost::mutex> lock(m_Mutex); 
      // Process event and change state 
      m_State = ...; 
      newState = m_State; 
      waitForPrevious = m_CurrentInvocation->get_future(); 
      m_CurrentInvocation.reset(new boost::promise<void>()); 
      releaseNext = m_CurrentInvocation; 
     } 
     // Wait for all previous invocations of the signal to finish 
     waitForPrevious.get(); 

     // Now it is our turn to invoke the signal 
     // TODO: use try-catch/scoped object to release next if an exception is thrown 
     OnStateChanged(newState); 

     // Allow the next state change to use the signal 
     releaseNext->set_value(); 
    } 

    // method to allow external objects to connect to the signal etc 
private: 
    boost::signals2::signal<void (State) > m_OnStateChanged; 
    boost::mutex m_Mutex; 
    State m_State; 
    // Initialized with a "fulfilled" promise in the constructor 
    // or do special handling of initially empty promise above 
    boost::shared_ptr<boost::promise<void> > m_CurrentInvocation; 
}; 

私の次のアプローチは、次のようなものになるだろう後になる。私の腸の感覚は、私がこのタイプの問題に遭遇した最初の人ではないと私に伝えます。私は自分の試したコードを試してテストしたコードを使うほうが好きです... :)私の質問は本当にあります:

既存の方法がありますかboost :: signals2シグナルの直列化された呼び出し(signals2ライブラリまたは共通パターンに組み込まれたものなど)を実現するには?

+0

"ただし、デッドロックにつながる可能性があるため、信号の呼び出しによってミューテックスを保持することはできません。あなたはこれを明確にすることができますか?シグナルハンドラの中には 'ObjectWithState'の状態を変更できるものがありますか?彼らは新しい 'OnEvent()'呼び出しを生成するでしょうか?無限回帰に入っていないことをどうやって確認しますか? – user1202136

+0

@ user1202136:はい。再帰呼び出しはデッドロックを取得する1つの方法です。あなたが2つのObjectWithStateを持っていて、状態変更のハンドラを設定して、場合によっては他の状態の変更を引き起こす場合は、より卑劣な方法です。これは、最初のミューテックスを保持し、もう一方をロックしようとするスレッドと、もう一方のミューテックスを保持し、最初のミューテックスをロックしようとするスレッドにつながります。したがって、一般的なガイドラインは、未知のコードを呼び出している間に決してロックを保持することではありません。http://drdobbs.com/article/print?articleId=202802983&siteSectionName=を参照してください。 – villintehaspam

答えて

0

次の解決策を提案します。保留中のシグナルのキューを作成し、別のスレッドでディスパッチさせます。コードはおおよそ次のようになります:

class ObjectWithState { 
private: 
    bool running; 
    std::queue<State> pendingSignals; 
    boost::condition_variable cond; 
    boost::mutex mut; 

    void dispatcherThread() 
    { 
     while (running) 
     { 
      /* local copy, so we don't need to hold a lock */ 
      std::vector<State> pendingSignalsCopy; 

      /* wait for new signals, then copy them locally */ 
      { 
       boost::unique_lock<boost::mutex> lock(mut); 
       cond.wait(mut); 
       pendingSignalsCopy = pendingSignals; 
       pendingSignals.clear(); 
      } 

      /* dispatch */ 
      while (!pendingSignalsCopy.empty()) 
      { 
       State newState = pendingSignalsCopy.front(); 
       OnStateChanged(newState); 
       pendingSignalsCopy.pop(); 
      } 
     } 
    } 

public: 
    void OnEvent() 
    { 
     State newState; 
     ... 

     /* add signal to queue of pending signals and wake up dispatcher thread */ 
     { 
      boost::unique_lock<boost::mutex> lock(mut); 
      pendingSignals.push(state); 
      cond.notify_all(); 
     } 
    } 
}; 
+0

これは、私が行っているのと同じことを部分的には達成することができますが、オブジェクトごとに個別のスレッドを専用にすることについては驚きではありません。だから次のものはすべてのオブジェクトのための1つのスレッドを共有することです - しかし、それでも1つのスレッドを専用にするのは少し厄介なので、スレッドプール全体に作業を分散したいと思うでしょう。また、この実装では、OnStateChanged関数が終了したときにすべてのリスナーに通知されることが保証されているわけではなく、場合によっては問題になる場合もあります(ただし、他の機能)。 – villintehaspam

+0

"また、この実装では、OnStateChanged関数が「どうしてですか?」というメッセージが表示されたときに、すべてのリスナーに通知されるとは限りません。 IIRCでは、boost :: signals2 :: signal :: operator()はすべてのスロットを反復処理してから復帰します。また、提案は基本的にwaitForPrevious.get()のすべてのスレッド(1つを除く)をブロックします。したがって、通知のためにスレッドを専用にする方が実際には良いかもしれません。 – user1202136

+0

OnStateChangedをOnEventに置き換えてください。混乱して申し訳ありません。ブロッキングの違いはまさに私が指摘していることですが、通知が行われたことを保証するためにスレッドをブロックする必要がある場合もあれば、スレッドを動かすことができる機能である場合もあります。 – villintehaspam

関連する問題