30.2SynchronizationObjects 469
the timestamp is 500 ms) on a wait object with ID 1013 named WaitMutex.
WaitThread is the name of the next thread in the list, and this thread is blocked
in a
WaitForMultipleObjects() call. It waits for an event named WaitEvent
with ID 1012, and it waits for the same mutex that our main thread has locked.
We can also see that we have already been waiting for 500 ms and that the speci-
fied timeout value is infinite. Finally, the last thread in the list is called
Dump-
Thread
. This is the thread that was used to output the status information, and it
has neither waits nor locks.
LockHelperClasses
An important practice when programming in multithreaded environments is to
release object ownership once a thread is done with its work. Of course, owner-
ship must also be released in case of an error, which is actually not that easy.
Common pitfalls include functions that do not have a single point of exit and un-
handled exceptions. To better illustrate this problem, take a look at the code in
Listing 30.5, which contains one obvious error where it fails to release the mutex
lock. In the case that an error occurs, we simply return false but forget to release
our lock. While this issue can be easily fixed by adding the missing
Unlock()
call before the
return statement, we still have another error hiding in the sample
code. Imagine that somewhere between the
Lock() and Unlock() calls, an ex-
ception occurs. If this exception does not occur within a try-catch block, we exit
the function, leaving the mutex in a locked state.
A simple and elegant solution to this problem is to provide RAII-style
3
lock-
ing using a little helper class. Our framework offers two different versions of this
class called
LockT and WaitLock. LockT is a simple template class that works
with all synchronization objects that implement a
Lock() and Unlock() method.
On construction, this class receives a reference to the guarded synchronization
object and, if requested, immediately locks it.
LockT() also offers explicit
Lock() and Unlock() functions, but the most important feature is that it unlocks
a locked object in its destructor. Listing 30.6 shows how to use
LockT() to fix all
issues in Listing 30.5.
The
WaitLock class implementation is almost identical to LockT’s—the only
difference is that it only works with synchronization objects derived from
Wai-
tObject
. There are two reasons why we do not use LockT for WaitObjects. The
first is that these objects are reference counted and need special handling when
passing a raw pointer to our helper class. The other reason is that
WaitObjects
3
RAII stands for resource acquisition is initialization. See Stroustrup, The Design and
Evolution of C++, Addison-Wesley, 1994.