31.2MultithreadingOverview 477
Figure 31.3. Sample timeline for producer-consumer model from Figure 31.2.
31.2MultithreadingOverview
Since the producer pushes items onto the head of the queue and the consumer
pops items off of the tail, the algorithm must serialize access between these
memory locations atomically. An atomic operation is an operation that appears to
all processors in the system as occurring instantly and is uninterruptible by other
processes or threads. Although C++ does not natively support atomic data types
(until C++0x), many libraries have other mechanisms to achieve the same results.
Serialization is typically supported by the OS and its API in various forms.
At the process level, a critical section can be used to synchronize access among
multiple threads, whereas a mutex can synchronize access among multiple pro-
cesses. When a thread enters a critical section, no other thread can execute the
code until that thread leaves the critical section. Finally, synchronization primi-
tives can be avoided in the design of a lock-free algorithm by exclusively using
atomic instructions to serialize resources. This requires detailed working
knowledge of the compiler, memory model, and processor.
31.3AFirstApproach:UsingWin32Semaphores
andCriticalSections
Since this chapter covers bounded queues, where each item is fixed in size, we
need a thread-safe way to internally manage the item count in the queue. Sema-
phores are an appropriate way to handle this.
Our first implementation of a FIFO, shown in Listing 31.1, uses a standard
circular queue, which is not thread safe. Note that the head precedes the tail but,
if wrapped around zero for an unsigned integer, continues to work because the
modulus is used for each array access. A common optimization is to use a power
of two for the queue size, allowing the modulus operation to be replaced with a
bitwise AND with one less than the queue size. To keep track of the number of
Producer
Consumer 0
Consumer 1
Consumer 2