VC++ Thread Synchronizatrion Using Events






2.88/5 (8 votes)
This article demonstrates how to synchronize two threads using events.
Introduction
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
The 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 SetEvent
and 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 <windows.h> #include <iostream.h> /*************GLOBALS************/ HANDLE hThread1 , hThread2; HANDLE hEvent1 , hEvent2; int g_nShared = 0; /* Global variable which is going to be accessed by both Threads */ DWORD WINAPI Thread1(LPVOID lParam) { while(1) { WaitForSingleObject(hEvent2,INFINITE); ResetEvent(hEvent2); cout << "Thread1()::g_nShared = " << ++g_nShared << endl; SetEvent(hEvent1); } return 0; } DWORD WINAPI Thread2(LPVOID lParam) { while(1) { WaitForSingleObject(hEvent1,INFINITE); ResetEvent(hEvent1); cout << "Thread2()::g_nShared = " << ++g_nShared << endl; SetEvent(hEvent2); } return 0; } void main() { // The Events which synchronize the 2 threads hEvent1 = CreateEvent(NULL,TRUE,FALSE,"Event1"); hEvent2 = CreateEvent(NULL,TRUE,FALSE,"Event2"); // Signal both the Events...so that they're up for grabs before // the threads are born!! SetEvent(hEvent1); SetEvent(hEvent2); // The Threads are Brought to Life ! hThread1 = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)&Thread1,NULL,0,0); hThread2 = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)&Thread2,NULL,0,0); while(1); // Don't let the Main Thread Die }
Explanation
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 CreateThread
function.
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.