Every software professional, in his career, would definitely have coded or come across or at least heard about multi-threaded applications. One of the most difficult tasks of writing a well-behaved multi-threaded application is the synchronization required between the various threads created in it. All versions of the Windows operating system provide synchronization objects to aid in the above-mentioned task. Events, Mutexes and Semaphores are a few synchronization objects available with Windows.
A common scenario in synchronization is that a thread that has finished doing its job has to wait for one or more of the other threads to complete their execution. The waiting thread usually is the main thread of the application, but could also be any other thread.
There are various ‘Wait Functions’ available using which the above-mentioned waiting is made possible. In this article I will try and explain each of the available wait functions and the differences between them.
The reader is expected to have a good understanding about windows programming and also should have a general idea about how multi-threaded applications work. Since the sample program is written using Visual C++ and MFC, a working knowledge of VC++ and MFC is also essential.
The basic principle on which all wait functions work is by waiting on an object handle. These functions can wait on the handles of objects such as Events, Mutexes, Semaphores, Threads, Processes, Waitable Timers etc. A time-out value can also be specified, which is the maximum amount of time the function waits.
All of the wait functions wait on the object handle or handles till some specified criteria is met. The two basic criteria for all these functions are the signaled state of the object on whose handle it is waiting and a time-out value. The calling thread waits till the object enters the signaled state and the time-out has expired. No processor time is used when the thread is in the wait state.
How an object becomes signaled depends on the type of object being dealt with. For example, an event object becomes signaled by calling the
SetEvent API, a mutex object becomes signaled when it is not owned by any thread, a waitable timer becomes signaled by calling the
SetWaitableTimer API and a thread becomes signaled when the thread function returns or when the
TerminateThread APIs are called.
The time-out value passed to the wait functions is specified in milliseconds. If the object does not enter the signaled state and the time-out expires, the function returns. The constant,
INFINITE can be passed as the time-out value, if the function needs to wait forever for the object to enter the signaled state.
INFINITE is actually defined as the hexadecimal value FFFFFFFF, which when converted, roughly accounts for about 50 days, which I guess is a lot of time for a function to wait.
Apart from these two criteria, some of the functions have a few other criteria, which will be seen in the following sections. The return value of these wait functions determine the reason why the function returned. In other words, the return value of the functions confirms the criteria it met.
Abbreviation used in the Sample Application
|WSO||Wait for Single Object|
|WMO||Wait for Multiple Objects|
|MWMO||Msg Wait for Multiple Objects|
|WSOE||Wait for Single Object Ex|
|WMOE||Wait for Multiple Objects Ex|
|MWMOE||Msg Wait for Multiple Objects Ex|
|SOAW||Signal Object And Wait|
About the Sample Application
Before I go into explaining the wait functions, I must give you a little information as to how the sample application is written, how it is to be used and what changes the reader is encouraged to make, so that he/she can better understand the working of the various wait functions.
Using the application is just a matter of two mouse clicks. The radio buttons select which wait function to be used and the Invoke button actually performs the execution. The statuses of all the threads are displayed against it at all times. The time (in seconds) waited by the wait function is also shown after the function returns.
The application includes a file by the name of Constants.h, which contains some constant values that the reader is encouraged to change. The application will then work in accordance with the new values after it has been re-compiled. The constants for each wait function have been given under separate sections in the Constants.h file. For example, the constants for the
WaitForSingleObject API is given as –
#define WSO_SLEEPTIME 3000
#define WSO_TIMEOUT INFINITE
In the implementation file
WaitFunctionsDlg.cpp, the functions dealing with a particular wait function are again given under separate sections. For example, the functions dealing with the
WaitForSingleObject API come under the section as shown below –
DWORD WINAPI WaitForSingleObjectProc(LPVOID)
The thread functions and the helper functions for each wait function are written separately for clarity. This may introduce a lot of code redundancy, but it is done so deliberately so that the reader can concentrate on one wait function at a time and avoid navigating through a lot of ‘if’ conditions and the like.
All thread entry point functions follow the naming convention of xxxxProc where xxxx is the name of the wait function and all helper functions follow the naming convention Invokexxxx where xxxx is again the name of the wait function.
With all that said, let’s get on with the wait functions.
This is the simplest of all the wait functions. It specifies only the two basic criteria. One single object handle and a time-out value. The thread calling this function will block till the function returns. The function returns either when the object enters the signaled state or when the time-out expires.
In the sample application, the function
InvokeWaitForSingleObject in the main thread creates another thread and waits on that thread handle as shown below –
m_ahThread = CreateThread(0, 0,
WaitForSingleObjectProc, 0, 0, 0);
The newly created thread simply sleeps for the time interval specified in WSO_SLEEPTIME and then returns. Until the thread function returns, the main thread, which is waiting on the handle of the other thread, is blocked at the wait call and will not respond to any messages. Check this by dragging the dialog box. Since the function completely blocks the thread, it is not a good idea to call it from the user interface thread.
This function is similar to the
WaitForSingleObject function except that it waits for one or more threads to complete rather than only one thread. Again the thread calling this function will block till the function returns. So as I said before, it is not a good idea to call it from a user interface thread.
The sample application creates multiple threads with 3 different time-out values. The first thread is created with a time-out value specified in
WMO_MINSLEEPTIME, the last thread is created with a time-out value specified in
WMO_MAXSLEEPTIME and all the other threads are created with a time-out value specified in
WaitForMultipleObjects returns either when the time-out expires or (depending on the value of
WMO_WAITFORALL) when one or all of the objects enter the signaled state. If the value of
TRUE then all of the objects have to enter the signaled state for the function to return and if it is
FALSE, the first object entering the signaled state causes the function to return.
This function is similar to the
WaitForMultipleObjects function except that it takes an addition parameter that specifies an event type. The event type could be a mouse event or a keyboard event or a timer event etc. The various event types are documented in MSDN as beginning with QS_. This event type is another criterion for the wait function to return.
For example, if
QS_HOTKEY is specified as the event type, the wait function checks to see if a
WM_HOTKEY message has been posted to the message queue of the calling thread.
The sample application creates multiple threads with 3 different time-out values similar to the case of
WaitForMultipleObjects. If the value of
TRUE, then the function waits either till the time-out value specified in
MWMO_TIMEOUT expires OR till all of the objects enter the signaled state AND the specified event occurs. If the value of
FALSE, then the function waits either till the time-out value specified in
MWMO_TIMEOUT expires OR till any of the objects enter the signaled state OR the specified event occurs. The event types are mentioned in
MWMO_EVENTS and many event types can be combined into this using the pipe (‘|’) operator.
If the values of
MWMO_EVENTS are specified as
QS_KEY respectively, then the function returns only when all of the objects enter the signaled state AND a keyboard message enters the message queue of the calling thread.
This function is similar to the
WaitForSingleObject function except that it takes an additional boolean parameter whose value decides whether the calling thread will be in an alertable wait state or not.
If this boolean parameter is set to TRUE, the function will return whenever a completion routine or asynchronous procedure call (APC) is queued. An APC is queued when the
QueueUserAPC API is called and a completion routine queuing happens when the
WriteFileEx APIs are completed. When the
WriteFileEx APIs are called, a completion function name is passed as parameter. When the actual read or write completes, the wait function returns and the completion function will be called by the system, provided the thread is in an alertable wait state.
Lets think of an example to better understand what the alertable wait state means. Assume you are doing inter-process communication (IPC) using named pipes or mailslots. Further assume that the client application has to wait for a thread to complete. At the same time, whenever a message arrives at the pipe, some operations have to be performed. This is possible by using the
WaitForSingleObjectEx API and giving the handle of the thread as the first parameter and setting the alertable parameter to TRUE.
In the sample application, an asynchronous file write operation is being done. The
WaitForSingleObjectEx API is called in a loop till the time-out expires or the object enters the signaled state. If the value of
WSOE_ALERTABLE is set to
TRUE, every time a file write completes, the wait function returns and the completion routine is invoked. Another asynchronous write is then performed and the wait function is called again. The completion routine simple advances a progress control. If the value of
WSOE_ALERTABLE is set to
WaitForSingleObjectEx API works just like the
This function is similar to the
WaitForMultipleObjects function except that it takes an additional alertable parameter as was the case with the
WaitForSingleObjectEx API. This function waits till the time-out expires or till one or more objects enter the signaled state or till a completion routine is queued.
In the sample application, this works similar to the case of the
WaitForMultipleObjects API except that an asynchronous file operation is in progress whose completion routine advances the progress control.
This function is a combination of all the other wait functions that we have seen so far. It is similar to the
MsgWaitForMultipleObjects API except for the additional alertable parameter. This function waits till the time-out expires or till one or more objects enter the signaled state or when the specified event occurs or till a completion routine is queued.
This function can be used in place of any of the above discussed wait functions. For example, in the sample application, setting
MWMOE_EVENTS to 0,
TRUE and specifying only one object handle will work exactly like the
This function is similar to the
WaitForSingleObjectEx API except that it takes an addition first parameter, which is a handle to a semaphore, mutex or event. When this API is called it first sets the object whose handle is passed as the first parameter to the signaled state and then it works just like the
For example, if the first parameter passed to the
SignalObjectAndWait API is the handle to an event object, we can say that it is actually the
SetEvent API followed by the
WaitForSingleObjectEx API rolled into one.
In the sample application, a thread is created as soon as this radio button is checked (Even before the Invoke button is clicked). This thread waits on an event using the
WaitForSingleObject API. When the
SignalObjectAndWait API is called, it first signals the event that the thread is waiting on. The remaining is exactly the same as in the case of the
There are a few more wait functions that do not deal with multi-threading and synchronization as such. I have included them in this article just because they belong to the same family of functions, i.e., functions that wait or block. These functions are not included in the sample program accompanying this article.
This is the most basic of the wait functions. It takes only one parameter, which is the time-out value.
Sleep blocks the calling thread till the specified timeout expires. If
INFINITE is passed as the time-out value, then killing the thread externally is the only means by which that thread can be exited. Variants of this function were available in the very early days of computing and can be found even in systems that do not support multi-threaded applications.
This function is a combination of the above-mentioned
Sleep function and the many ‘Ex’ functions mentioned above, for example,
WaitForSingleObjectEx. In addition to the time-out parameter, it takes a second boolean parameter, which indicates whether the calling thread enters an alertable wait state. That is, the function blocks till the specified time-out expires or till a completion routine is queued.
This function blocks the execution of the calling thread until a new message arrives in the message queue of the thread. Messages already present in the message queue, which have been checked by the thread but not removed (e.g. Using
PM_NOREMOVE etc.), will not be considered as a new message and the function continues to block the thread in such a case.
This function is a little different from the other above-mentioned functions, in that, it waits only on a handle to a process (not thread) and not on any other synchronization objects. This function takes a time-out value as parameter in addition to the handle to a process. It waits either till the time-out expires or till there are no more input messages pending for the given process. In other words, till the process is waiting for user input.
The Unix operating system supports the concept of a parent-child relationship amongst processes, which is not available in Windows. In Unix, the parent process can wait for a child process to finish executing using the
waitpid system calls. Using
WaitForInputIdle, a similar concept can be simulated, where a process can wait for another process, which it created using the
CreateProcess API, to finish processing all messages.
I hope this article has helped in the understanding of the basics of the ‘Wait Functions’ and how these functions are actually used. There are numerous other wait functions also available but not mentioned in this article, like
WaitForDebugEvent etc., which come under hardware, debugging etc. Many other libraries like MAPI, DirectX etc. also contains many more wait functions.