Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / Win32

Thread Synchronization Using the Win32 Event Object

4.43/5 (4 votes)
3 Nov 2011CPOL4 min read 76.3K   985  
Thread synchronization using the Win32 Event object.

Introduction

C/C++ programs can use Win32 event objects in a number of situations to notify a waiting thread of the occurrence of an event. Such as, an overlapped I/O operation on files, named pipes, and communication devices use an event object to signal their completion.

Win32 Events

A Win32 Event works like a state machine and spends its life between two states, i.e., signaled state and non-signaled state. An event is in signaled state means that it has the capacity to release the threads waiting for this event to be signaled. An event is in non-signaled state means that it will not release any thread that is waiting for this particular event. The Event has two types: manual reset event and auto reset event. Manual event has a signaled user set to non-signaled state, and uses the ResetEvent function manually. The auto reset event automatically occurs when the object is raised to the non-signaled state.

The CreateEvent function is used to create the event thread synchronization object. The manual or auto reset event choice is mentioned in the CreateEvent function parameter initialization. The Wait family functions (WaitForSingleObject, WaitForMultipleObjects) are used to wait when a particular event occurs. The group of objects wait for the events: a single object is signaled or the entire events are signaled in the thread. This function can create named and unnamed event objects. The SetEvent function is used to set the event object to signaled state. The ResetEvent function is used to set the event object to non-signaled state. If the function is successful, it returns the handle of that event. If the named event is already available, the GetLastError function returns the ERROR_ALREADY_EXISTS flag. If the named event is already available, the OpenEvent function is used to access the event previously created by the CreateEvent function.

Code Description

The following example uses event objects to demonstrate how child threads signal events to inform about its completion to the main thread. The main thread which is waiting for multiple threads ensures that all the threads complete before executing further.

C++
//    Create an Manual Reset Event where events must be reset 
//    manually to non signaled state
HANDLE     hEvent1 = CreateEvent ( NULL , true , false , L"MyEvent1" );
if ( !hEvent1 ) return -1;

HANDLE     hEvent2 = CreateEvent ( NULL , true , false , L"MyEvent2" );
if ( !hEvent2 ) return -1;

HANDLE     hEvent3 = CreateEvent ( NULL , true , false , L"MyEvent3" );
if ( !hEvent3 ) return -1

We create three events MyEvent1, MyEvent2, and MyEvent3, respectively. Use CreateEvent ( NULL , true, false , "MyEvent" ); to create an event. The parameter descriptions are given below:

  1. The first parameter NULL represents the default security attributes.
  2. The second parameter is a flag to the Manual reset event. false means the event will be an auto reset event, and manual reset event if the flag is true.
  3. The third parameter is a flag to the state of the event being created. If false, the event will be created in non-signaled state, and if true, the event will be created in signaled state. An event being created in signaled state means that the first thread which is waiting for the signal will be released without any call to SetEvent(...); in case of an auto reset event. In case of a manual reset event, all threads will be released that are waiting for this signal unless there is a call to ResetEvent(...).
  4. The fourth parameter is the name of the event with which it will be identified globally. If an event with the same name as above already exists, then a handle to the existing event will open.
C++
// Array to store thread handles 
    HANDLE Array_Of_Events_Handles[3];
// create a Thread which will wait for the events to occur
DWORD Id;
HANDLE hThrd1 = CreateThread ( NULL, 0, (LPTHREAD_START_ROUTINE)ThreadFun1,0,0,&Id );
if ( !hThrd1 ) { CloseHandle (hEvent1); return -1; }
HANDLE hThrd2 = CreateThread ( NULL, 0, (LPTHREAD_START_ROUTINE)ThreadFun2,0,0,&Id );
if ( !hThrd2 ) { CloseHandle (hEvent2); return -1; }
HANDLE hThrd3 = CreateThread ( NULL, 0, (LPTHREAD_START_ROUTINE)ThreadFun3,0,0,&Id );
if ( !hThrd3 ) { CloseHandle (hEvent3); return -1; }
Array_Of_Events_Handles[0] = hEvent1;
    Array_Of_Events_Handles[1] = hEvent2;
Array_Of_Events_Handles[2] = hEvent3;    
// Wait until all events are signaled.
while(1)
{
    //WaitForMultipleObjects( 3, Array_Of_Events_Handles, TRUE, 20000);
    MyWaitForMultipleObjects(3, Array_Of_Events_Handles, TRUE, 20000);
    break;
}

Three separate threads are launched with their respective methods. These thread functions will simulate their task by sleeping for 5, 10, 15 seconds at the time of their execution. The event handles are stored in an array Array_Of_Events_Handles so that the main thread can wait on these handles.

C++
DWORD WINAPI ThreadFun1( LPVOID n )
{
    cout<<"Thread Instantiated 1........."<<endl;

    // Get the handler to the event for which we need to wait in 
    //    this thread.
    HANDLE hEvent = OpenEvent ( EVENT_ALL_ACCESS , false, L"MyEvent1" );
    if ( !hEvent ) { return -1; }

    Sleep ( 5000 );

    ResetEvent ( hEvent );
    // Signal the event
    if (SetEvent ( hEvent ))
    {
        cout<<"Got The signal - MyEvent 1......."<<endl;
    }    

    CloseHandle ( hEvent );
    cout<<"End of the Thread 1......"<<endl;
    return 0;
}

Above is an example of one of the thread functions. This basically opens the already created event and resets it manually after sleeping for 5 seconds.

Now since we are waiting on all the three thread handles until it signals out by 'ResetEvent ( hEvent )', the threads are synchronized and the main thread waits for their completion before moving ahead for execution. The output is full of console logs to give you a feel of the sequence of events.

1.png

Points of interest

'WaitForMultipleObjects' is limited to wait on the MAXIMUM_WAIT_OBJECTS handles count, which is 64. If we need to wait on more than MAXIMUM_WAIT_OBJECTS handles, we can create a separate thread to wait on MAXIMUM_WAIT_OBJECTS and then do a wait on these threads to finish. Using this method, we can create MAXIMUM_WAIT_OBJECTS threads and each of those can wait for MAXIMUM_WAIT_OBJECTS object handles. Refer to this blog. In some practical scenarios, the creation of many threads doesn’t seem to be risk free and there could be a thread synchronization overhead. Especially when you have just too many number of handles to wait on and all handles are supposed to be signaled before the waiting thread can proceed further. I have written my own extension of WaitForAllObjects which solves this and is useful in scenarios where more than 64 handles are supposed to be signaled and waited on, i.e., 'bWaitAll = TRUE'.

C++
DWORD MyWaitForMultipleObjects (DWORD _NumHandles, HANDLE* _Handles, 
           BOOL bWaitAll = TRUE, DWORD dwTimeoutMS = INFINITE )
{
    HRESULT hr = S_OK;

    DWORD dwWaitResult = WAIT_FAILED;

    if ((_NumHandles > MAXIMUM_WAIT_OBJECTS) && !bWaitAll)
    {
    cout << "MyWaitForMultipleObjects: WARNING: too many objects" 
         << _NumHandles << MAXIMUM_WAIT_OBJECTS;
    return dwWaitResult;
    }

    // Create tracking array
    // Handles will be removed as they are signaled
    DWORD       NumHandles  = _NumHandles;
    HANDLE*     Handles     = new HANDLE [_NumHandles + 1];
    for (DWORD i = 0; i < NumHandles; i++)
    {
        Handles[i]   = _Handles[i];
    }

    bool bDone      = false;
    bool bTimeout   = false;
    do
    {
    dwWaitResult = 
    WaitForMultipleObjects(min(NumHandles, (MAXIMUM_WAIT_OBJECTS)), 
                           Handles, /*bWaitAll*/false, INFINITE); 

        if (dwWaitResult == WAIT_FAILED)
        {
            cout<<"WAIT_FAILED(MyWaitForMultipleObjects)"<<endl;
        }
        else if (dwWaitResult == WAIT_TIMEOUT)
        {
            //timeout - we're done
            bTimeout = true;
        }
        else if ((dwWaitResult >= WAIT_OBJECT_0) && 
             (dwWaitResult <= (WAIT_OBJECT_0 + NumHandles - 1)))
        {
            // A Handle has signaled
            if (bWaitAll)
            {
                // remove the handle from the array
        DWORD Index = dwWaitResult - WAIT_OBJECT_0;
                memmove(&Handles[Index],   &Handles[Index+1], 
               (NumHandles-Index)*sizeof(HANDLE));
        Handles[NumHandles-1]      = 0;
        NumHandles--;

                if (NumHandles == 0)
                {
                    bDone = true;
                    dwWaitResult = WAIT_OBJECT_0;
                }
            }
            else
            {
                // we are done since bWaitAll is not set
                bDone = true;
            }
        }
    } while (!bDone && SUCCEEDED(hr));

    delete [] Handles;

    return dwWaitResult;
}

The above code basically does WaitForMultipleObjects in a loop for the first 64 handles in an array 'Handles'. As and when events/handles signal out, they are removed from the 'Handles' array and the remaining handles are pushed in the array. This continues till all the handles are waited for their signal.

History

  • 3/11/11: Initial release.
  • 4/11/11: First update - based on comments, corrected the array to hold event handles.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)