Interesting and challenging as it sounds, thread synchronization is still a major ouch! for an intermediate developer. This article, although not an expert insight into thread programming, serves as a primer for developers seeking to synchronize threads using events. There are many different synchronization objects (mutexes, semaphores, Critical Sections etc.) that can be used, but for the specific case that is going to be discussed, I think events serve the best purpose for synchronization. But as always, I am welcome to suggestions!
Understanding this article requires a basic understanding of multi-threaded programming.
The case in question: A main thread spawns two threads. It is required to execute each thread alternately, i.e., first Thread 1, then Thread 2, then again Thread 1, Thread 2 etc.
A Few Basics
Event object is created using the
CreateEvent function. The event can be either in a signaled or non-signaled state. Signaled means that the
Event has released some waiting thread. Non-signaled means that all threads waiting for this event need to wait for it to become signaled. The
ResetEvent functions can be used for putting the Event in the Signaled and Non-Signaled states, respectively. Basically, an event signifies to the process that something has happened. The waiting threads can then act accordingly once this event has occurred.
Let the Coding Commence
#include <span class="code-keyword"><windows.h></span>
#include <span class="code-keyword"><iostream.h></span>
HANDLE hThread1 , hThread2;
HANDLE hEvent1 , hEvent2;
int g_nShared = 0;
DWORD WINAPI Thread1(LPVOID lParam)
cout << "Thread1()::g_nShared = " << ++g_nShared << endl;
DWORD WINAPI Thread2(LPVOID lParam)
cout << "Thread2()::g_nShared = " << ++g_nShared << endl;
hEvent1 = CreateEvent(NULL,TRUE,FALSE,"Event1");
hEvent2 = CreateEvent(NULL,TRUE,FALSE,"Event2");
hThread1 = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)&Thread1,NULL,0,0);
hThread2 = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)&Thread2,NULL,0,0);
Since there are two threads, we need to have two event objects to synchronize them, i.e., we need to make one thread wait till the other thread has finished executing a cycle. Each thread waits on an event object to be signaled. The other thread changes the state of this event object to signaled once it has completed executing one of its cycles.
The program starts with the
main() function creating the two event objects, which are manual in nature. Hence, they need to be first put in the Signaled state before the threads are created. If you create and signal the events after creating the threads, then the program will wait indefinitely without any output on the console screen.
After this, the threads are spawned using the
Thread1 executes first, since it is created first. It waits in the
while loop for the
hEvent2 object to get signaled. But, since
hEvent2 is already signaled for the first execution of the loop, the next line is executed immediately. The next line puts the
hEvent2 object in the non-signaled state by calling the function
ResetEvent(). It then changes the value of the shared global
g_nShared and then signals the
hEvent1 object. Although this statement has no effect in the first execution of the loop, it plays an important role later. The point of execution returns to the beginning of the
while loop where Thread 1 waits for the
hEvent2 object to be signaled. This will get signaled by Thread 2 later. So, till that time, Thread 1 will have to wait.
Thread 2 is then executed since the
hEvent1 object that it is waiting for is already signaled. Similar to Thread 1, it non-signals the
hEvent1 object, changes the value of
g_nShared, and then signals the
hEvent2 object which causes Thread 1 to get executed again and Thread 2 to wait on the
hEvent1 object to get signaled. This alternating cycles of loops of Thread 1 and Thread 2 continues till the program is terminated by the user.