Using SignalObjectAndWait
The consumer loop in the preceding code segment is critical to the CV model because it waits for a state change and then tests to see if the desired state holds. The state may not hold if the event is too coarse, indicating, for example, that there was simply some state change, not necessarily the one that is required. Furthermore, a different consumer thread might have made some other state change, such as emptying the message buffer. The loop required two waits and a mutex release, as follows:
while (!cvp (&State)) {
ReleaseMutex (State.Guard);
WaitForSingleObject (State.CvpSet, TimeOut);
WaitForSingleObject (State.Guard, INFINITE);
}
The time-out on the first wait (the event wait) is required to avoid missed signals and other potential problems. This code will work under Windows 9x as well as NT 3.5 (another obsolete Windows version), and the code segment will also work if you replace the mutexes with CSs.However, with Windows NT 5.x (XP, 2000, and 2003) and even NT 4.0, we can use SignalObjectAndWait, an important enhancement that eliminates the need for the time-out and combines the mutex release with the event wait. In addition to the program simplicity benefit, performance generally improves because a system call is eliminated and there is no need to tune the wait time-out period.
DWORD SignalObjectAndWait (
HANDLE hObjectToSignal,
HANDLE hObjectToWaitOn,
DWORD dwMilliseconds,
BOOL bAlertable)
This function simplifies the consumer loop, where the two handles are the mutex and event handles, respectively. There is no time-out because the calling thread waits on the second handle immediately after the first handle is signaled (which, in this case, means that the mutex is released). The signal and wait are atomic so that no other thread can possibly signal the event between the time that the calling thread releases the mutex and the thread waits on the second handle. The simplified consumer loop, then, is as follows.
while (!cvp (&State)) {
SignalObjectAndWait (State.Guard, State.CvpSet,
INFINITE, FALSE);
WaitForSingleObject (State.Guard, INFINITE);
}
The final argument, bAlertable, is FALSE here but will be set to trUE in the later sections on APCs.In general, the two handles can be for any appropriate synchronization objects. You cannot, however, use a CRITICAL_SECTION as the signaled object; kernel objects are necessary.All program examples, both in the book and on the Web site, use SignalObjectAndWait, although some alternative solutions are also included on the Web site and are mentioned in the text. If Windows 9x operation is required, replace it with the signal/wait pair in the original code segment, and be certain to have a finite time-out period on the wait.The section on APCs shows a different technique to signal waiting threads which has the additional advantage of signaling a specific waiting thread, whereas, when using events, there is no easy way to control which thread is signaled.