5

オブジェクトからの状態変化に関する通知が明確に定義された順序でスロットに到達するようにするために、boost::signals2 シグナルのマルチスレッド呼び出しをシリアル化したいと考えています。

バックグラウンド

マルチスレッド プログラムに内部状態を持つオブジェクトがあります。内部状態の一部はプログラムの他の部分にとって興味深いものであり、オブジェクトは次のような boost::signals2 シグナルを使用して状態の変化を公開します。

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 ハンドラーの同時呼び出しが複数ある場合、実際に変更が行われた順序とは別の順序で状態の変更がリスナーに通知される可能性があります。状態自体は上記のようなミューテックスによって保護されているため、実際の状態は明確に定義されています。ただし、デッドロックが発生する可能性があるため、シグナルの呼び出し全体でミューテックスを保持することはできません。これは、シグナルの実際の呼び出しが任意の順序で発生する可能性があることを意味しますが、状態の変化が実際に発生したのと同じ順序で呼び出す必要があります。

この問題を処理する 1 つの方法は、状態をシグナルから削除し、状態が変化したことをリスナーに通知することです。その後、オブジェクトの状態を照会し、信号が発生したときのオブジェクトの状態またはそれ以降の状態を取得します。私のシナリオでは、リスナーはすべての状態変更について通知を受ける必要があるため、この方法はここでは機能しません。

私の次のアプローチは、次のようなものになります。

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 ライブラリや一般的なパターンに組み込まれているなど)?

4

1 に答える 1

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();
        }
    }
};
于 2012-04-04T09:22:53.863 に答える