Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Halt! Who Goes There?

0.00/5 (No votes)
18 Sep 2003 1  
Demonstrates the various wait functions used for thread synchronization in a multi-threaded application

WaitFunctions Dialog

Introduction

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.

Pre-Requisite

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.

General Description

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 signalled state of the object on whose handle it is waiting and a time-out value. The calling thread waits till the object enters the signalled state and the time-out has expired. No processor time is used when the thread is in the wait state.

How an object becomes signalled depends on the type of object being dealt with. For example, an event object becomes signalled by calling the SetEvent API, a mutex object becomes signalled when it is not owned by any thread, a waitable timer becomes signalled by calling the SetWaitableTimer API and a thread becomes signalled when the thread function returns or when the ExitThread or TerminateThread APIs are called.

The time-out value passed to the wait functions is specified in milliseconds. If the object does not enter the signalled 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 signalled 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 is 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:

//####################### Constants for WaitForSingleObject

#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:

//## Functions for handling the WaitForSingleObject request

//# Thread Function
DWORD WINAPI WaitForSingleObjectProc(LPVOID)

//# Helper Function
void CWaitFunctionsDlg::InvokeWaitForSingleObject()

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.

Wait Functions

WaitForSingleObject

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 signalled 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[0] = CreateThread(0, 0,
            WaitForSingleObjectProc, 0, 0, 0);

WaitForSingleObject(m_ahThread[0], WSO_TIMEOUT);

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.

WaitForMultipleObjects

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 WMO_MIDSLEEPTIME.

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 signalled state. If the value of WMO_WAITFORALL is TRUE, then all of the objects have to enter the signalled state for the function to return and if it is FALSE, the first object entering the signalled state causes the function to return.

MsgWaitForMultipleObjects

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 MWMO_WAITFORALL is TRUE, then the function waits either till the time-out value specified in MWMO_TIMEOUT expires OR till all of the objects enter the signalled state AND the specified event occurs. If the value of MWMO_WAITFORALL is FALSE, then the function waits either till the time-out value specified in MWMO_TIMEOUT expires OR till any of the objects enter the signalled 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_TIMEOUT, MWMO_WAITFORALL and MWMO_EVENTS are specified as INFINITE, TRUE and QS_KEY respectively, then the function returns only when all of the objects enter the signalled state AND a keyboard message enters the message queue of the calling thread.

WaitForSingleObjectEx

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 ReadFileEx or WriteFileEx APIs are completed. When the ReadFileEx or 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.

Let's 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 signalled 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 FALSE, the WaitForSingleObjectEx API works just like the WaitForSingleObject API.

WaitForMultipleObjectsEx

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 signalled 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.

MsgWaitForMultipleObjectsEx

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 signalled 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, MWMOE_ALERTABLE to FALSE, MWMOE_WAITFORALL to TRUE and specifying only one object handle will work exactly like the WaitForSingleObject API.

SignalObjectAndWait

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 signalled state and then it works just like the WaitForSingleObjectEx API.

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 WaitForSingleObjectEx API.

Other Functions

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.

Sleep

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.

SleepEx

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.

WaitMessage

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 GetQueueStatus or PeekMessage with PM_NOREMOVE etc.), will not be considered as a new message and the function continues to block the thread in such a case.

WaitForInputIdle

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 wait or 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.

Conclusion

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 WaitCommEvent, WaitForDebugEvent, etc., which come under hardware, debugging, etc. Many other libraries like MAPI, DirectX, etc. also contain many more wait functions.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here