457
30
ACrossPlatform
MultithreadingFramework
Martin Fleisz
Thinstuff s.r.o.
Over the last couple of years, a new trend in game engine design has started due
to changing processor designs. With the increasing popularity of multicore CPUs,
game engines have entered the world of parallel computing. While older engines
tried to avoid multithreading at all costs, it is now a mandatory technique in order
to be able to take full advantage of the available processing power.
To be able to focus on the important parts of a game engine, like the graphics
engine or the networking code, it is useful to have a robust, flexible, and easy to
use multithreading framework. This framework also serves as an abstraction lay-
er between the platform-dependent thread and the platform-independent game
engine code. While the C++0x standard provides support for threading, develop-
ers still have to refer to external libraries like Boost to gain platform-independent
threading support. The design and interface of the Boost threading framework
strongly resembles the POSIX Threads (or Pthreads) library, which is a rather
low-level threading library. In contrast, our framework offers a higher-level inter-
face for threading and synchronization. What we provide is a collection of easy
to use synchronization objects, a flexible and simple way of handling threads,
and additional features like deadlock detection. We also show a few examples of
how to extend and customize our framework to your own needs.
30.1Threading
ThreadManager
Let us first take a look at the core component of our threading framework, the
ThreadManager class. As can be seen in the sample code on the website, this
458 30.ACrossPlatformMultithreadingFramework
class is implemented using a simple singleton pattern [Meyers 1995]. We decided
to use this pattern for two reasons. First, we want global access to the
Thread-
Manager
in order to be able to start a thread from any location in our engine. The
second reason is that we want to have just one instance of this class at a time be-
cause we use it to create snapshots of our application’s current state.
The
ThreadManager has several responsibilities in our framework. Whenev-
er we start a new thread, the manager attaches a
ThreadInfo object to the new
thread instance. The
ThreadManager also provides a unique ID for each syn-
chronization object. Upon creation, a synchronization object registers with the
ThreadManager, which keeps a complete list of all existing threads and synchro-
nization objects. This information is used to create a snapshot of the application
that shows us which threads exist and in what state the currently used synchroni-
zation objects are. Using this information, we can get an overview of our applica-
tion run-time behavior, helping us to find deadlocks or performance bottlenecks.
ThreadInfo
The most important component in our framework, when working with threads, is
the
ThreadInfo class. In order to be able to uniquely identify a thread, our
ThreadInfo class stores a thread name and a thread ID. This information can be
used during logging in order to identify which thread wrote which log entry.
ThreadInfo also offers methods to wait for a thread, to stop it, or to kill it, alt-
hough killing a thread should usually be avoided.
Another important feature of
ThreadInfo is the recording of wait object in-
formation. Whenever a synchronization object changes its wait state, it stores that
information in the
ThreadInfo’s wait object information array. With this infor-
mation, the
ThreadManager is able to construct a complete snapshot of the
threading framework’s current state. Additionally, each thread is able to detect
whether there are any pending wait operations left before it is stopped (e.g., a
mutex might still be locked after an exception occurred). In this case, it can issue
a warning as such a scenario could potentially lead to a deadlock.
Another feature that we have built into our
ThreadInfo objects is active
deadlock detection. Using a simple trigger system, we can easily create a watch-
dog service thread that keeps checking for locked threads. In order for this fea-
ture to work, a thread has to continuously call the
TriggerThread() method of
its
ThreadInfo instance. If the gap between the last TriggerThread() call and
the current timestamp becomes too large, a thread might be deadlocked. Do not
use too small values (i.e., smaller than a second) in order to prevent accidental
timeouts. If a thread is known to be blocking, you can temporarily disable the
trigger system using the
SetIgnoreTrigger() method. You can also use this

Get Game Engine Gems 2 now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.