Windows Thread Synchronization 6






Interprocess Synchronization


Multiple processes can have handles to the same event, mutex, semaphore, or timer object, so these objects can be used to accomplish interprocess synchronization. The process that creates an object can use the handle returned by the creation function (CreateEvent(), CreateMutex(), CreateSemaphore(), or CreateWaitableTimer()). Other processes can open a handle to the object by using its name, or through inheritance or duplication.


Object Names


Named objects provide an easy way for processes to share object handles. After a process has created a named event, mutex, semaphore, or timer object, other processes can use the name to call the appropriate function (OpenEvent(), OpenMutex(), OpenSemaphore(), or OpenWaitableTimer()) to open a handle to the object. Name comparison is case sensitive. The names of event, semaphore, mutex, waitable timer, file-mapping, and job objects share the same name space. If you try to create an object using a name that is in use by an object of another type, the function fails and GetLastError() returns ERROR_INVALID_HANDLE. Therefore, when creating named objects, use unique names and be sure to check function return values for duplicate-name errors. If you try to create an object using a name that is in use by an object of same type, the function succeeds, returning a handle to the existing object, and GetLastError() returns ERROR_ALREADY_EXISTS. For example, if the name specified in a call to the CreateMutex() function matches the name of an existing mutex object, the function returns a handle to the existing object. In this case, the call to CreateMutex() is equivalent to a call to the OpenMutex() function. Having multiple processes use CreateMutex() for the same mutex is therefore equivalent to having one process that calls CreateMutex() while the other processes call OpenMutex(), except that it eliminates the need to ensure that the creating process is started first. When using this technique for mutex objects, however, none of the calling processes should request immediate ownership of the mutex. If multiple processes do request immediate ownership, it can be difficult to predict which process actually gets the initial ownership.

A Terminal Services environment has a global name space for events, semaphores, mutexes, waitable timers, file-mapping objects, and job objects. In addition, each Terminal Services client session has its own separate name space for these objects. Terminal Services client processes can use object names with a "Global\" or "Local\" prefix to explicitly create an object in the global or session name space. Fast user switching is implemented using Terminal Services sessions (each user logs into a different session). Kernel object names must follow the guidelines outlined for Terminal Services so that applications can support multiple users. Windows 2000:  If Terminal Services is not running, the "Global\" and "Local\" prefixes are ignored. The remainder of the name can contain any character except the backslash character. Synchronization objects can be created in a private namespace.


Object Inheritance


When you create a process with the CreateProcess() function, you can specify that the process inherit handles to mutex, event, semaphore, or timer objects using the SECURITY_ATTRIBUTES structure. The handle inherited by the process has the same access to the object as the original handle. The inherited handle appears in the handle table of the created process, but you must communicate the handle value to the created process. You can do this by specifying the value as a command-line argument when you call CreateProcess(). The created process then uses the GetCommandLine() function to retrieve the command-line string and convert the handle argument into a usable handle.


Object Duplication


The DuplicateHandle() function creates a duplicate handle that can be used by another specified process. This method of sharing object handles is more complex than using named objects or inheritance. It requires communication between the creating process and the process into which the handle is duplicated. The necessary information (the handle value and process identifier) can be communicated by any of the interprocess communication methods, such as named pipes or named shared memory.


Other Synchronization Mechanisms


The following are other synchronization mechanisms that available in Windows APIs:


  1. Overlapped input and output
  2. Asynchronous procedure calls
  3. Critical section objects
  4. Condition variables
  5. Slim reader/writer locks
  6. One-time initialization
  7. Interlocked variable access
  8. Interlocked singly linked lists
  9. Timer queues
  10. The MemoryBarrier macro


Synchronization and Overlapped Input and Output


You can perform either synchronous or asynchronous (also called overlapped) I/O operations on files, named pipes, and serial communications devices. The WriteFile(), ReadFile(), DeviceIoControl(), WaitCommEvent(), ConnectNamedPipe(), and TransactNamedPipe() functions can be performed either synchronously or asynchronously. The ReadFileEx() and WriteFileEx() functions can be performed only asynchronously. When a function is executed synchronously, it does not return until the operation has been completed. This means that the execution of the calling thread can be blocked for an indefinite period while it waits for a time-consuming operation to finish. Functions called for overlapped operation can return immediately, even though the operation has not been completed. This enables a time-consuming I/O operation to be executed in the background while the calling thread is free to perform other tasks. For example, a single thread can perform simultaneous I/O operations on different handles, or even simultaneous read and write operations on the same handle.

To synchronize its execution with the completion of the overlapped operation, the calling thread uses the GetOverlappedResult() function or one of the wait functions to determine when the overlapped operation has been completed. You can also use the HasOverlappedIoCompleted() macro to poll for completion. To cancel all pending asynchronous I/O operations, use the CancelIoEx() function and provide an OVERLAPPED structure that specifies the request to cancel. Use the CancelIo() function to cancel pending asynchronous I/O operations issued by the calling thread for the specified file handle. Overlapped operations require a file, named pipe, or communications device that was created with the FILE_FLAG_OVERLAPPED flag. When a thread calls a function (such as the ReadFile() function) to perform an overlapped operation, the calling thread must specify a pointer to an OVERLAPPED structure. (If this pointer is NULL, the function return value may incorrectly indicate that the operation completed.) All of the members of the OVERLAPPED structure must be initialized to zero unless an event will be used to signal completion of an I/O operation. If an event is used, the hEvent member of the OVERLAPPED structure specifies a handle to the allocated event object. The system sets the state of the event object to nonsignaled when a call to the I/O function returns before the operation has been completed. The system sets the state of the event object to signaled when the operation has been completed. An event is needed only if there will be more than one outstanding I/O operation at the same time. If an event is not used, each completed I/O operation will signal the file, named pipe, or communications device. When a function is called to perform an overlapped operation, the operation might be completed before the function returns. When this happens, the results are handled as if the operation had been performed synchronously. If the operation was not completed, however, the function's return value is FALSE, and the GetLastError() function returns ERROR_IO_PENDING. A thread can manage overlapped operations by either of two methods:


  1. Use the GetOverlappedResult() function to wait for the overlapped operation to be completed.
  2. Specify a handle to the OVERLAPPED structure's manual-reset event object in one of the wait functions and then call GetOverlappedResult() after the wait function returns. The GetOverlappedResult() function returns the results of the completed overlapped operation, and for functions in which such information is appropriate; it reports the actual number of bytes that were transferred.


When performing multiple simultaneous overlapped operations on a single thread, the calling thread must specify an OVERLAPPED structure for each operation. Each OVERLAPPED structure must specify a handle to a different manual-reset event object. To wait for any one of the overlapped operations to be completed, the thread specifies all the manual-reset event handles as wait criteria in one of the multiple-object wait functions. The return value of the multiple-object wait function indicates which manual-reset event object was signaled, so the thread can determine which overlapped operation caused the wait operation to be completed. It is safer to use a separate event object for each overlapped operation, rather than specify no event object or reuse the same event object for multiple operations. If no event object is specified in the OVERLAPPED structure, the system signals the state of the file, named pipe, or communications device when the overlapped operation has been completed. Thus, you can specify these handles as synchronization objects in a wait function, though their use for this purpose can be difficult to manage because, when performing simultaneous overlapped operations on the same file, named pipe, or communications device, there is no way to know which operation caused the object's state to be signaled. A thread should not reuse an event with the assumption that the event will be signaled only by that thread's overlapped operation. An event is signaled on the same thread as the overlapped operation that is completing. Using the same event on multiple threads can lead to a race condition in which the event is signaled correctly for the thread whose operation completes first and prematurely for other threads using that event. Then, when the next overlapped operation completes, the event is signaled again for all threads using that event, and so on until all overlapped operations are complete.

Be careful when reusing OVERLAPPED structures. If an application reuses OVERLAPPED structures on multiple threads and calls GetOverlappedResult() with the bWait parameter set to TRUE, the application must ensure that the associated event is set before the application reuses the structure. This can be accomplished by using the WaitForSingleObject() function after calling GetOverlappedResult() to force the thread to wait until the operation completes. Note that the event object must be a manual-reset event object. If an autoreset event object is used, calling GetOverlappedResult() with the bWait parameter set to TRUE causes the function to be blocked indefinitely.





< Thread Synchronization 5 | Thread Synchronization Programming | Win32 Programming | Thread Synchronization 7 >