Существует ли существующий способ сериализации вызовов сигнала boost :: signal2? - PullRequest
5 голосов
/ 30 января 2012

Я хотел бы сериализовать многопоточные вызовы сигнала boost :: signal2, чтобы убедиться, что уведомления об изменениях состояния объекта поступают в слоты в четко определенном порядке.

Фон

У меня есть объект с внутренним состоянием в многопоточной программе. Некоторые части внутреннего состояния интересны для других частей программы, и объект демонстрирует изменения состояния с помощью сигнала boost :: signal2, аналогичного следующему:

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, это может потенциально привести к тому, что слушатели будут уведомлены об изменениях состояния в другом порядке, чем изменения фактически имели место. Само состояние защищено мьютексом, как описано выше, поэтому состояние фактическое хорошо определено. Однако мьютекс не может удерживаться во время вызова к сигналу, так как это может привести к тупикам. Это означает, что фактические вызовы сигнала могут происходить в любом порядке, тогда как я бы потребовал, чтобы они вызывались в том же порядке, в котором фактически произошли изменения состояния.

Одним из способов решения этой проблемы было бы удалить State из сигнала и просто уведомить слушателей, что состояние изменилось. Затем они могут запросить объект на предмет его состояния и получить состояние, которое было у объекта при срабатывании сигнала или в более позднем состоянии . В моем сценарии слушатели должны быть проинформированы об изменениях состояния all , чтобы этот метод здесь не работал.

Мой следующий подход будет выглядеть примерно так:

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 :: signal2 (например, встроенный в библиотеку signal2 или общий шаблон)?

1 Ответ

0 голосов
/ 04 апреля 2012

Предлагаю следующее решение. Создайте очередь ожидающих сигналов и отправьте их в отдельный поток. Код будет выглядеть примерно так:

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();
        }
    }
};
...