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.
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:
- The first parameter
NULL
represents the default security attributes. - 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
. - 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(...)
. - 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.
HANDLE Array_Of_Events_Handles[3];
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;
while(1)
{
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.
DWORD WINAPI ThreadFun1( LPVOID n )
{
cout<<"Thread Instantiated 1........."<<endl;
HANDLE hEvent = OpenEvent ( EVENT_ALL_ACCESS , false, L"MyEvent1" );
if ( !hEvent ) { return -1; }
Sleep ( 5000 );
ResetEvent ( hEvent );
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.
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
'.
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;
}
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, false, INFINITE);
if (dwWaitResult == WAIT_FAILED)
{
cout<<"WAIT_FAILED(MyWaitForMultipleObjects)"<<endl;
}
else if (dwWaitResult == WAIT_TIMEOUT)
{
bTimeout = true;
}
else if ((dwWaitResult >= WAIT_OBJECT_0) &&
(dwWaitResult <= (WAIT_OBJECT_0 + NumHandles - 1)))
{
if (bWaitAll)
{
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
{
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.