Other Thread Synchronization Functions

[Previous] [Next]

WaitForSingleObject and WaitForMultipleObjects are the most commonly used functions for performing thread synchronization. However, Windows offers a few more functions that have slight variations. If you understand WaitForSingleObject and WaitForMultipleObjects, you'll have no trouble understanding how these other functions work. In this section, I'll briefly introduce some of them.

Asynchronous Device I/O

Asynchronous device I/O allows a thread to start a read or write operation without having to wait for the read or write operation to complete. For example, if a thread needs to load a large file into memory, the thread can tell the system to load the file into memory. Then, as the system loads the file, the thread can be busy performing other tasks—creating windows, initializing internal data structures, and so on. When the initialization is complete, the thread can suspend itself, waiting for the system to notify it that the file has been read.

Device objects are synchronizable kernel objects, which means that you can call WaitForSingleObject, passing the handle of a file, socket, communication port, and so on. While the system performs the asynchronous I/O, the device object is in the nonsignaled state. As soon as the operation is complete, the system changes the state of the object to signaled so that the thread knows that the operation has completed. At this point, the thread continues execution.

WaitForInputIdle

A thread can also suspend itself by calling WaitForInputIdle:

 DWORD WaitForInputIdle( HANDLE hProcess, DWORD dwMilliseconds); 

This function waits until the process identified by hProcess has no input pending in the thread that created the application's first window. This function is useful for a parent process. The parent process spawns a child process to do some work. When the parent process's thread calls CreateProcess, the parent's thread continues to execute while the child process initializes. The parent's thread might need to get the handle of a window created by the child. The only way for the parent's thread to know when the child process has been fully initialized is to wait until the child is no longer processing any input. So after the call to CreateProcess, the parent's thread places a call to WaitForInputIdle.

You can also use WaitForInputIdle when you need to force keystrokes into an application. Let's say that you post the following messages to the main window of an application:

WM_KEYDOWN with a virtual key of VK_MENU
WM_KEYDOWN with a virtual key of VK_F
WM_KEYUP with a virtual key of VK_F
WM_KEYUP with a virtual key of VK_MENU
WM_KEYDOWN with a virtual key of VK_O
WM_KEYUP with a virtual key of VK_O

This sequence sends Alt+F, O to an application, which, for most English-language applications, chooses the Open command from the application's File menu. This command opens a dialog box, but before the dialog box can appear, Windows must load the dialog box template from the file and cycle through all the controls in the template, calling CreateWindow for each one. This can take some time. So the application that posted the WM_KEY* messages can call WaitForInputIdle, which causes the application to wait until the dialog box has been completely created and is ready for user input. The application can now force additional keys into the dialog box and its controls so that it can continue doing whatever it needs to do.

Developers who wrote for 16-bit Windows often faced this problem. Applications wanted to post messages to a window but didn't know exactly when the window was created and ready. The WaitForInputIdle function solves this problem.

MsgWaitForMultipleObjects(Ex)

A thread can call the MsgWaitForMultipleObjects or MsgWaitForMultipleObjectsEx functions to cause the thread to wait for its own messages:

 DWORD MsgWaitForMultipleObjects( DWORD dwCount, PHANDLE phObjects, BOOL fWaitAll, DWORD dwMilliseconds, DWORD dwWakeMask); DWORD MsgWaitForMultipleObjectsEx( DWORD dwCount, PHANDLE phObjects, DWORD dwMilliseconds, DWORD dwWakeMask, DWORD dwFlags); 

These functions are similar to the WaitForMultipleObjects function. The difference is that they allow a thread to be scheduled when a kernel object becomes signaled or when a window message needs dispatching to a window created by the calling thread.

A thread that creates windows and performs user-interface related tasks should use MsgWaitForMultipleObjectsEx instead of WaitForMultipleObjects because the latter prohibits the thread's user-interface from responding to the user. This function is discussed in more detail in Chapter 27.

WaitForDebugEvent

Windows has excellent debugging support built right into the operating system. When a debugger starts executing, it attaches itself to a debuggee. The debugger simply sits idle, waiting for the operating system to notify it of debug events related to the debuggee. A debugger waits for these events by calling the WaitForDebugEvent function:

 BOOL WaitForDebugEvent( PDEBUG_EVENT pde, DWORD dwMilliseconds); 

When a debugger calls this function, the debugger's thread is suspended. The system notifies the debugger that a debug event has occurred by allowing the call to WaitForDebugEvent to return. The structure pointed to by the pde parameter is filled by the system before it awakens the thread. This structure contains information about the debug event that has just occurred.

SignalObjectAndWait

The SignalObjectAndWait function signals a kernel object and waits on another kernel object in a single atomic operation:

 DWORD SignalObjectAndWait( HANDLE hObjectToSignal, HANDLE hObjectToWaitOn, DWORD dwMilliseconds, BOOL fAlertable); 

When you call this function, the hObjectToSignal parameter must identify a mutex, semaphore, or an event. Any other type of object causes the function to return WAIT_FAILED, and GetLastError returns ERROR_INVALID_HANDLE. Internally, the function examines the type of object and performs the equivalent of ReleaseMutex, ReleaseSemaphore (with a count of 1), or ResetEvent, respectively.

The hObjectToWaitOn parameter can identify any of the following kernel objects: mutex, semaphore, event, timer, process, thread, job, console input, and change notification. As usual, the dwMilliseconds parameter indicates how long the function should wait for this object to become signaled, and the fAlertable flag indicates whether the thread should be able to process any queued asynchronous procedure calls while the thread is waiting.

The function returns one of the following values: WAIT_OBJECT_0, WAIT_TIMEOUT, WAIT_FAILED, WAIT_ABANDONED (discussed earlier in this chapter), or WAIT_IO_COMPLETION.

This function is a welcome addition to Windows for two reasons. First, because you often need to signal one object and wait on another, having a single function that does both operations saves processing time. Each time you call a function that causes your thread to jump from user-mode to kernel-mode code, approximately 1000 CPU cycles need to execute (on x86 platforms). For example, code such as this causes at least 2000 CPU cycles to execute:

 ReleaseMutex(hMutex); WaitForSingleObject(hEvent, INFINITE); 

In high-performance server applications, SignalObjectAndWait saves a lot of processing time.

Second, without the SignalObjectAndWait function, one thread cannot know when another thread is in a wait state. This knowledge is useful for functions such as PulseEvent. As mentioned earlier in this chapter, PulseEvent signals an event and immediately resets it. If no threads are currently waiting on the event, no events catch the pulse. I've seen people write code like this:

 // Perform some work.  SetEvent(hEventWorkerThreadDone); WaitForSingleObject(hEventMoreWorkToBeDone, INFINITE); // Do more work.  

A worker thread performs some code and then calls SetEvent to indicate that the work is done. Another thread executes code like this:

 WaitForSingleObject(hEventWorkerThreadDone); PulseEvent(hEventMoreWorkToBeDone); 

The worker thread's code fragment is poorly designed because it does not work reliably. After the worker thread calls SetEvent, the other thread might wake up immediately and call PulseEvent. The worker thread is preempted and hasn't had a chance to return from its call to SetEvent, let alone call WaitForSingleObject. The result is that the signaling of the hEventMoreWorkToBeDone event is missed entirely by the worker thread.

If you rewrite the worker thread's code to call SignalObjectAndWait as shown here, the code will work reliably because the signaling and wait is performed atomically.

 // Perform some work.  SignalObjectAndWait(hEventWorkerThreadDone, hEventMoreWorkToBeDone, INFINITE, FALSE); // Do more work.  

When the nonworker thread wakes up, it can be 100 percent sure that the worker thread is waiting on the hEventMoreWorkToBeDone event and is therefore guaranteed to see the event pulsed.

Windows 98
Windows 98 does not have a useful implementation for this function.



Programming Applications for Microsoft Windows
Programming Applications for Microsoft Windows (Microsoft Programming Series)
ISBN: 1572319968
EAN: 2147483647
Year: 1999
Pages: 193

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net