4.1.2 Interprocess Synchronization

The Mutex, Semaphore, and Event types may be used by the threads of one or more processes. Each type has its own set of functions for creating an object or modifying its state; but they share the same functions— WaitForSingleObject and WaitForMultipleObjects—to implement the wait operation. Both wait functions allow you to wait indefinitely for the object or objects to be Signalled; or alternatively, you can specify a timeout interval after which the wait is aborted. The two generic wait functions may not be used with Critical Section objects. When an application creates one of the three interprocess synchronization objects, space is allocated in system memory, the object is initialized, and a handle to it is returned to the creating process. This handle can be inherited by child processes or passed to unrelated processes to be duplicated. Or, the creating process can associate a name with the object to be used by other processes in opening a handle to the object. An object is garbage-collected by the system when the last handle to the object is closed. Note that open handles are closed when a process exits.

A Mutex object is either owned (Not-Signalled) or unowned (Signalled). When a thread waits for a Mutex object, it is requesting ownership. If it is currently owned by another thread, the waiting thread will be blocked until the ownership is released. For example, two or more threads that communicate through a shared memory segment could use a Mutex object to avoid simultaneously writing to the memory. Before executing the sections of code in which the shared memory is accessed, each thread would use one of the wait functions to request ownership of the Mutex. And when finished using the memory, the thread would release its ownership. So any other thread requesting ownership of the Mutex would be blocked if the memory was currently in use. The thread that owns a Mutex can make additional wait calls on the same Mutex object without blocking, but it must release ownership once for each time that a wait was satisfied.

A Semaphore object maintains a count; and its state is Signalled as long as its count is greater than zero. A maximum count is specified when a Semaphore object is created, and each time that a wait operation is satisfied by the object, its count is decremented. When a thread releases the object, its count is incremented. This is useful in controlling a shared resource that can support a limited number of multiple users. Suppose, for example, that an application launches multiple child processes to evaluate a problem. To avoid performance degradation, the application limits the number of processes at any one time. It creates a Semaphore object with a count initialized to some maximum number of processes, and before a child process is executed, the parent process calls a wait function to see if the Semaphore is Signalled. As each child is started, the count is decremented; and if the maximum number of child processes are executing, the count is zero, the Semaphore state is Not-Signalled, the wait function blocks, and no more processes are launched. As each child terminates, it releases the Semaphore object (which it inherits from the parent). This increments the count, so additional processes can be started.

An Event object provides a signalling mechanism to notify one or more threads that an event has occurred. The Event functions allow you to set (to a Signalled state) or reset (to a Not-Signalled state) an Event object. You can also pulse an Event, which sets the Event briefly and then automatically resets it. Two types of Event objects can be created:

Manual Reset When a Manual Reset Event is set to Signalled, all waiting threads will be released until the Event is explicitly reset to Not-Signalled. A Manual Reset Event might be used by a process with several threads, where one thread writes to a region of shared memory and the other threads read from it. The writing thread could reset the Event to Not-Signalled when it is writing, to temporarily block the readers. When through writing, it could set the Event to Signalled to allow all the other threads to read freely until the next write operation occurs. Pulsing a Manual Reset Event will release all waiters before the automatic reset.
Auto Reset When an Auto Reset Event is set to Signalled, a single waiting thread is released and then the Event is automatically reset to Not-Signalled, blocking other waiters until the Event is again set to Signalled. This can be used for releasing one of several waiting threads. For example, a master thread could distribute tasks to several worker threads using a loop that prepares the data for one task and then signals an Auto Reset Event to release one of the waiting workers to process it. An Auto Reset Event would also be used by a monitor thread to signal the occurrence of an interesting event. For example, an application could have several threads each of which monitors a device. When an event occurs, the monitor signals its Auto Reset Event to notify the application, and the Event object automatically resets so the monitor can use it again the next time an event occurs. Pulsing an Auto Reset Event will release a single waiter before the automatic reset.