In the last articles we saw how to synchronize two threads using critical section and mutex. They allow both the threads to execute at the same time. But only one thread can access the blocking object at a time. However, sometimes we may wish to write two threads where one thread should not get executed unless the second thread signals it to do so or unless some event takes place. For example, if one thread (thread A) fills a buffer with data and another thread (thread B) processes that data, then thread B must wait for a signal from thread A saying that the buffer has been initialized. This is possible using Win32 event objects that have been encapsulated in the CEvent class. An event is a flag in the OS kernel. This flag can enjoy two states: set or reset. A set event is said to be in signaled state, whereas, a reset event is said to be in non-signaled state. Like a mutex, events too can be used within a process or across the processes.
Windows supports two different types of events: autoreset events and manual-reset events. The difference between them is that an autoreset event is automatically reset to the nonsignaled (unavailable) state when a thread blocking on it is released. A manual-reset event doesn’t reset automatically; it must be reset programmatically. An autoreset event is used when just one thread is blocked by the event. Manual reset event is used when two or more threads are blocked by the event.
Let us see an example in which events are used to synchronize two threads belonging to the same process. In this example one thread reads information about a line i.e. co-ordinates, color and thickness from a file and stores the information in an object array (of type CObArray). The other thread displays the lines according to the details stored in the CObArray. Naturally, the thread that displays the lines should not start unless CObArray is initialized by the other thread.
Create an SDI application ‘Events’ with Doc/View architecture support. Add a menu ‘Display’ (having id ID_DISPLAY) to the existing menu. Create a global structure in ‘eventsView.h’ as given below:
struct data : public CObject
{
CPoint stpt, endpt ;
int thick ;
COLORREF rgb ;
} ;
Note that we have derived the structure from CObject class since we will be storing address of objects of this structure in a CObArray. Add variable m_pdc of type CDC* and a static variable m_ob of type CObArray to the CEventsView class. Add the following statement in ‘eventsView.cpp’:
CObArray CEventsView::m_ob ;
Create a global object of CEvent class as shown below:
CEvent event ( FALSE, FALSE ) ;
The first parameter, specifies whether the event object is initially signaled (TRUE) or nonsignaled (FALSE). Second parameter specifies whether the event is a manual-reset event (TRUE) or an autoreset event (FALSE). The third parameter assigns a name to the event object. This parameter is used if the threads are in different processes. The final parameter is a pointer to a SECURITY_ATTRIBUTES structure describing the object’s security attributes. We have specified only first two parameters, for others default values are assumed.
Set the initial size of CObArray object by calling CObArray::SetSize( ) from the constructor as given below:
CEventsView::CEventsView( )
{
m_ob.SetSize ( 0, 10 ) ;
}
The first parameter passed to the SetSize( ) function specifies the initial size of array. The second parameter is the number of elements to be allocated to the array if increase in size is necessary.
Add a handler OnDisplay( ) for the ‘Display’ menu. The code of OnDisplay( ) is given below:
void CEventsView::OnDisplay( )
{
AfxBeginThread ( datathread, &m_ob ) ;
m_pdc = new CClientDC ( this ) ;
AfxBeginThread ( dispthread, m_pdc ) ;
}
Here, we have started two threads, datathreads and dispthread. The datathread reads the information of lines from file and stores it in an array, whereas, dispthread displays the lines in the window. Before starting dispthread we have initialised the data member m_pdc and passed it to the thread.
The thread function datathread( ) is given below:
UINT datathread ( LPVOID param )
{
CStdioFile f ;
if ( f.Open ( “objects.dat”, CFile::modeRead ) == 0 )
{
AfxMessageBox ( “File Not Found” ) ;
return 1 ;
}
CString str ;
int left, top, right, bottom, r, g, b, th ;
data *m_dt ;
while ( f.ReadString ( str ) != NULL )
{
sscanf ( str, “%d %d %d %d %d %d %d %d”, &left, &top,
&right, &bottom, &r, &g, &b, &th ) ;
m_dt = new data ;
m_dt -> stpt = CPoint ( left, top ) ;
m_dt -> endpt = CPoint ( right, bottom ) ;
m_dt -> thick = th ;
m_dt -> rgb = RGB ( r, g, b ) ;
CEventsView::m_ob.Add ( m_dt ) ;
}
f.Close( ) ;
event.SetEvent( ) ;
return 0 ;
}
Here, we have opened the data file (This file should be created as an ordinary text file.) The information present in the data file is given below:
90 100 250 210 0 255 0 2
100 100 100 210 0 255 0 2
Here, first four digits are the co-ordinates of line, next are the RGB elements and last digit is the thickness of pen. Next read the data and store them in the elements of data structure. The object of the structure is then added to the array by calling CObArray::Add( ) function. Once all the records have been stored in the array we have released the dispthread by calling CEvent::SetEvent( ) function. If there are multiple threads locked on an event they are released by calling CEvent::PulseEvent( ) function. PulseEvent( ) function sets the state of the event to signaled state, releases any waiting threads, and resets it to non-signaled state automatically.
The dispthread( ) function is given below:
UINT dispthread ( LPVOID param )
{
CDC *pdc = ( CDC * ) param ;
event.Lock( ) ;
data *pd ;
CPen mypen ;
int count = CEventsView::m_ob.GetSize( ) ;
for ( int i = 0 ; i < count ; i++ )
{
pd = ( data* ) CEventsView::m_ob[i] ;
if ( mypen.m_hObject != NULL )
mypen.DeleteObject( ) ;
mypen.CreatePen ( PS_SOLID, pd -> thick, pd -> rgb ) ;
pdc -> SelectObject ( &mypen ) ;
pdc -> MoveTo ( pd -> stpt ) ;
pdc -> LineTo ( pd -> endpt ) ;
}
return 0 ;
}
Here, we have first locked the thread by calling CEvent::Lock( ) function until datathread sets the event. Then we have retrieved the number of elements stored in the array using CObarray::GetSize( ) function. In a loop we have obtained the address of the objects of data structure stored in the array and have drawn the lines using member functions of CDC class.
Suppose we don’t synchronize the threads. Since both the threads have been started simultaneously the thread dispthread would get executed even before the array has been initialized. In such a case CObArray::GetSize( ) function would return 0. In such a case the loop would not execute and so the program would not display any output.