Я хотел бы сериализовать многопоточные вызовы сигнала 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 или общий шаблон)?