Game Development Reference
In-Depth Information
Listing 25-4. Creating a
mutex
std::mutex m_mutex;
std::unique_lock<std::mutex> m_finishedQueryLock{ m_mutex, std::defer_lock };
We have two parts to our
mutex
, the mutex itself and a wrapper template named
unique_lock
, which
provides convenient access to the behavior of the
mutex
. The
unique_lock
constructor takes a
mutex
object as its main parameter. This is the
mutex
that it acts on. The second parameter is optional;
if it is not supplied, the
unique_lock
obtains a lock on the
mutex
immediately but by passing
std::defer_lock
we can prevent this from happening.
At this point you might be wondering exactly how a
mutex
works. A
mutex
can be locked and
unlocked. We class the process of locking a
mutex
as obtaining a lock. The
unique_lock
template
provides three methods to work with the mutex:
lock
,
unlock
, and
try_lock
.
The
lock
method is a
blocking
call. This means that your thread's execution will stall until the
mutex
has been successfully locked by the
thread
you called
lock
from. If the
mutex
is already locked by
another thread, your thread will wait until the
mutex
becomes unlocked before proceeding.
The
unlock
method unlocks a locked
mutex
. Best practice is to hold your lock for as few lines
of code as possible. Generally this means that you should do any calculations you need before
obtaining a lock, obtain the lock, write the result to the shared variable, and then unlock immediately
to allow other threads to lock the
mutex
.
The
try_lock
method is a nonblocking version of
lock
. This method returns
true
if the lock was
obtained or
false
if the lock was not obtained. This allows you to do other work, usually in a loop
within the thread until such time that the
try_lock
method returns
true
.
Now that you have seen the code to create a lock, I can show you how to use the
unique_lock
template to prevent your Text Adventure game from crashing. Listing 25-5 uses the
lock
to protect
access to the
m_playerQuit
and
m_playerWon
variables in the
HasFinished
method.
Listing 25-5. Updating
Game::HasFinished
with the
unique_lock
bool HasFinished() const
{
m_finishedQueryLock.lock();
bool hasFinished = m_playerQuit || m_playerWon;
m_finishedQueryLock.unlock();
return hasFinished;
}
The
HasFinished
method now calls the
lock
method on m_
finishedQueryLock
before it calculates
the value to be stored in the
hasFinished
variable. The lock is released before the return statement in
the method to allow any waiting
threads
to be able to lock the
mutex
.
This is only the first step in being able to protect our program from crashes. The
HasFinished
method is called on the main
thread
but the
m_playerWon
and
m_playerQuit
variables are written
to from the game
thread
. I have added three new methods to protect these variables in the game
thread
in Listing 25-6.