Introduction
Thread synchronization is a problem domain as there are many ways to handle it. There are enough books and articles that show how to avoid multiple threads related nightmare. While working on this nightmare, I come across WIN32 events (WIN32 Kernel objects). Initially, I was unable to understand how to use global kernel objects across threads using global variables; that itself creates hell, but after understanding it, I found it very easy to use. Here, I will explain the usage of WIN32 events in context of Auto and Manual Reset Events.
About WIN32 Events
WIN32 Events are kernel objects and like other kernel objects available across process boundaries. 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.
Using WIN32 Auto Reset Event
An Auto Reset Event is the event which will guarantee to release a single thread that is waiting on this event to occur and returns back to non signaled state. If more than one thread is waiting for this event to occur then which thread will be released is random.
Creating and using WIN32 Events make use of WIN32 APIs. Some of the APIs are:
CreateEvent(...);
CreateThread(...);
WaitForSingleObject (...);
WaitForMultipleObject (...);
OpenHandle(...);
SetEvent(...);
ResetEvent(...);
CloseHandle(...);
Let's see the code which will use an auto reset event. The scenario is that the main program will create a thread and the thread will wait for an event to get signaled twice. The main program will signal the event and wait for the thread to die.
#include <windows.h>
#include <iostream>
using namespace std;
DWORD WINAPI Tf ( LPVOID n )
{
cout<<"Thread Instantiated........."<<endl;
HANDLE hEvent = OpenEvent ( EVENT_ALL_ACCESS , false, "MyEvent" );
if ( !hEvent ) { return -1; }
for ( char counter = 0; counter < 2; counter ++ )
{
WaitForSingleObject ( hEvent, INFINITE );
cout<<"Got The signal......."<<endl;
}
CloseHandle(hEvent);
cout<<"End of the Thread......"<<endl;
return 0;
}
int main()
{
HANDLE hEvent = CreateEvent ( NULL , false , false , "MyEvent" );
if ( !hEvent ) return -1;
DWORD Id;
HANDLE hThrd = CreateThread ( NULL, 0, (LPTHREAD_START_ROUTINE)Tf,0,0,&Id );
if ( !hThrd ) { CloseHandle (hEvent); return -1; }
Sleep ( 1000 );
for ( char counter = 0; counter < 2; counter ++ )
{
SetEvent ( hEvent );
Sleep ( 2000 );
}
WaitForSingleObject ( hThrd, INFINITE );
CloseHandle ( hThrd );
CloseHandle ( hEvent );
cout<<"End of Main ........"<<endl;
return 0;
}
Code Description
The above code calls CreateEvent ( NULL , false , false , "MyEvent" );
to create an event. The parameter descriptions are given below:
- First parameter
NULL
represents default security attributes.
- Second parameter is a flag to Manual reset event.
false
means the event will be an auto reset event, and manual reset event if the flag is true
.
- 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 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 Manual Reset Event, all threads will be released that are waiting for this signal unless there is a call of ResetEvent(...)
.
- 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 handle to the existing event will open.
The code then creates a thread by calling CreateThread(...)
API. We can also use C run time library function beginthreadex(...)
for creating the thread. The loop runs twice and signals the events after two seconds using SetEvent(...)
API. This API takes the handle to the thread.
After signaling the event, the main program waits for the thread to die using WaitForSingleObject(...)
. The program must close the handle because leaving the handle will have a memory leak.
The thread uses OpenEvent ( EVENT_ALL_ACCESS , false, "MyEvent" );
to get the handle to the event. Note that we can access the events only by unique names, so names must be unique. The parameter descriptions are given below:
- First parameter allows the thread to wait for this particular event signal.
- Second parameter will not allow the events to be inheritable.
- Third parameter is the name of the event for which we required a handle.
Using WIN32 Manual Reset Event
An event is called Manual reset event if it has the capacity of releasing as many number of threads which are waiting for this particular event until there is an explicit API call for resetting the event in non signaled state. Creating a manual reset event can be done just by changing the second parameter of the CreateEvent(...);
API call. To create a manual reset event, we will call this API as CreateEvent ( NULL , true , false , "MyEvent" );
.
Rest of the program will be similar to that of the auto reset event except that we need to call ResetEvent(...)
API. The code is given below:
#include <windows.h>
#include <iostream>
using namespace std;
DWORD WINAPI Tf ( LPVOID n )
{
cout<<"Thread Instantiated........."<<endl;
HANDLE hEvent = OpenEvent ( EVENT_ALL_ACCESS , false, "MyEvent" );
if ( !hEvent ) { return -1; }
for ( char counter = 0; counter < 2; counter ++ )
{
WaitForSingleObject ( hEvent, INFINITE );
ResetEvent ( hEvent );
cout<<"Got The signal......."<<endl;
}
CloseHandle ( hEvent );
cout<<"End of the Thread......"<<endl;
return 0;
}
int main()
{
HANDLE hEvent = CreateEvent ( NULL , true , false , "MyEvent" );
if ( !hEvent ) return -1;
DWORD Id;
HANDLE hThrd = CreateThread ( NULL, 0, (LPTHREAD_START_ROUTINE)Tf,0,0,&Id );
if ( !hThrd ) { CloseHandle (hEvent); return -1; }
Sleep ( 1000 );
for ( char counter = 0; counter < 2; counter ++ )
{
SetEvent ( hEvent );
Sleep ( 2000 );
}
WaitForSingleObject ( hThrd, INFINITE );
CloseHandle ( hThrd );
CloseHandle ( hEvent );
cout<<"End of Main ........"<<endl;
return 0;
}
History
In continuation of this, I will explain the usage and difference between different Event signaling APIs like PulseEvent(...)
and SetEvent(...)
etc., in my next article.