В настоящее время я изучаю, как делать многопоточность на С++. Один из моих обучающих проектов - игра в тетрис. В этом проекте у меня есть класс Game, который содержит все данные состояния игры. Он имеет методы перемещения блока вокруг и несколько других вещей. Этот объект будет доступен пользователю (который будет использовать клавиши со стрелками для перемещения блока, из основного потока), и в то же время поточный таймер реализует гравитацию на активном блоке (периодически опуская его).
Сначала мне показалось, что я могу сделать поток класса Game безопасным, добавив переменную-член mutex и заблокирую ее внутри каждого вызова метода. Но проблема заключается в том, что он защищает только вызовы отдельных методов, а не изменения, связанные с несколькими вызовами методов. Например:
// This is not thread-safe.
while (!game.isGameOver())
{
game.dropCurrentBlock();
}
Одно из решений, которое я пробовал, - это добавить метод доступа для переменной mutex, чтобы заблокировать ее также извне:
// Extra scope added to limit the lifetime of the scoped_lock.
{
// => deadlock, unless a recursive mutex is used
boost::mutex::scoped_lock lock(game.getMutex());
while (!game.isGameOver())
{
game.dropCurrentBlock();
}
}
Однако это будет заторможено, если не используется рекурсивный мьютекс. Теперь, глядя на несколько сообщений на StackOverflow, похоже, большинство из них категорически не одобряет использование рекурсивных мьютексы.
Но если рекурсивные мьютексы не являются опциями, не означает ли это, что становится невозможным создать поточно-безопасный класс (поддерживающий скоординированные изменения)?
Единственное допустимое решение, похоже, никогда не блокирует мьютекс внутри вызовов метода, и вместо этого всегда полагается на пользователя делать блокировку снаружи.
Однако, если это так, то не было бы лучше просто оставить класс Game таким, какой он есть, и создать класс-оболочку, который связывает объект Game с мьютексом?
Update
Я передал идею обертки и создал класс под названием ThreadSafeGame (cpp), который выглядит следующим образом:
class ThreadSafeGame
{
public:
ThreadSafeGame(std::auto_ptr<Game> inGame) : mGame(inGame.release) {}
const Game * getGame() const
{ return mGame.get(); }
Game * getGame()
{ return mGame.get(); }
boost::mutex & getMutex() const
{ return mMutex; }
private:
boost::scoped_ptr<Game> mGame;
mutable boost::mutex mMutex;
};
// Usage example, assuming "threadSafeGame" is pointer to a ThreadSafeGame object.
{
// First lock the game object.
boost::mutex::scoped_lock lock(threadSafeGame->getMutex());
// Then access it.
Game * game = threadSafeGame->getGame();
game->move(Direction_Down);
}
Он имеет тот же недостаток, что он зависит от того, как пользователь блокирует мьютекс снаружи. Но, кроме этого, это кажется мне пригодным решением.
Я делаю это правильно?