В вашем случае у вас есть игра большого класса, которая требует синхронизации.Вы уже отметили проблему, когда каждый метод синхронизирован, но один стиль не может выполнять операции безопасно.
Если мы посмотрим на класс ThreadSafeGame, я думаю, что интерфейс для него можно улучшить, чтобы мы могли толькополучить доступ к состоянию игры, если мы находимся в синхронизированном режиме.Есть несколько способов сделать это.Один из способов - заставить getGame возвращать класс, который одновременно удерживает блокировку и экземпляр.Вы определяете operator-> для этого класса, чтобы он возвращал Game *.Когда класс уничтожается, блокировка освобождается.
В моих примерах используются некоторые функции C ++ 0x (лямбда-выражения, семантика перемещения, auto и decltype), но сделать его совместимым с C ++ 98 не исключено.
Я покажу другой способ сделать это, используя метод посещения:
template<typename TValue>
struct threadsafe_container : boost::noncopyable
{
explicit threadsafe_container (TValue && value)
: m_value (std::move (value))
{
}
// visit executes action when have the lock
template<typename TAction>
auto visit (TAction action) -> decltype (action (m_value))
{
boost::mutex::scope_lock lock (&m_mutex);
TValue & value (m_value);
return action (value);
}
private:
boost::mutex m_mutex;
TValue m_value;
};
// Extra paranthesis necessary otherwise c++ interprets it as a function declaration
threadsafe_container<game> s_state ((ConstructAGameSomehow ()));
void EndTheGame ()
{
s_state.visit ([](game & state)
{
// In here we are synchronized
while (!state.is_game_over ())
{
state.drop_current_block ();
}
});
}
bool IsGameOver ()
{
return s_state.visit ([](game & state) {return state.is_game_over ();});
}
И метод класса блокировки:
template<typename TValue>
struct threadsafe_container2 : boost::noncopyable
{
struct lock : boost::noncopyable
{
lock (TValue * value, mutex * mtx)
: m_value (value)
, m_lock (mtx)
{
}
// Support move semantics
lock (lock && l);
TValue * get () const
{
return m_value;
}
TValue * operator-> () const
{
return get ();
}
private:
TValue * m_value;
boost::mutex::scope_lock m_lock;
};
explicit threadsafe_container2 (TValue && value)
: m_value (std::move (value))
{
}
lock get ()
{
return lock (&m_value, &m_mutex);
}
private:
boost::mutex m_mutex;
TValue m_value;
};
// Extra paranthesis necessary otherwise c++ interprets it as a function declaration
threadsafe_container2<game> s_state ((ConstructAGameSomehow ()));
void EndTheGame ()
{
auto lock = s_state2.get ();
// In here we are synchronized
while (!lock->is_game_over ())
{
lock->drop_current_block ();
}
}
bool IsGameOver ()
{
auto lock = s_state2.get ();
// In here we are synchronized
reutrn lock->is_game_over ();
}
Но основная идея та же,Убедитесь, что мы можем получить доступ к состоянию игры только тогда, когда у нас есть блокировка.Конечно, это C ++, поэтому мы всегда можем найти способы нарушить правила, но процитируем Херба Саттера: Защити от Мерфи, а не от Макиавелли, т.е.Защитите себя от ошибок, а не от программистов, которые намереваются нарушать правила (они всегда найдут способ сделать это)
Теперь ко второй части комментария:
Грубозернистая блокировка противмелкозернистая блокировка?Крупнозернистый довольно прост в реализации, но страдает от проблем с производительностью, точная блокировка очень сложна, но может иметь лучшую производительность.
Я бы сказал;сделать все возможное, чтобы избежать блокировки полностью.С этим я не имею в виду;скрестите мои пальцы и надеюсь, что я не получу условия гонки.Я имею в виду структурирование вашей программы так, чтобы только один поток управлял изменяемым состоянием и изолировал это изменяемое состояние, чтобы оно не могло быть изменено по ошибке несколькими потоками.
В вашем случае у вас есть входной поток, принимающий пользовательские входные данные и обновленияштат.Один поток обновляет состояние игры по таймеру.
Вместо этого поток ввода, принимающий состояние пользователя, отправляет сообщение в поток менеджера состояний игры со словами: «Это то, что сделал пользователь».Затем поток состояния игры потребляет сообщения и действует соответствующим образом.Таким образом, игровое состояние доступно только этому потоку, и никакие условия гонки и мертвые блокировки не могут возникнуть.
Это иногда называют «Активным шаблоном объекта».
Читатели оповещений говорят: Но, эй, очередь сообщений должна быть поточно-ориентированной!Это правда, но очередь сообщений сравнительно тривиальна, чтобы сделать потокобезопасным.
IMO, этот шаблон является одним из наиболее важных для создания поддерживаемых параллельных проектов.