Vous êtes sur la page 1sur 21

1

Multithreading in Windows
A thread is a separate path of execution within a program. Windows is a
preemptive multithreading operating system which manages threads. Each program is
assigned a single thread of execution by default.

Multithreading has many benefits in windows applications, such as:


• Each child window in an MDI application can be assigned to a different
thread.
• If the drawing part (OnDraw code) of the program takes a long time to
execute, the GUI will be blocked until the redraw is completed. However,
we can assign a separate thread to the OnDraw function, thus causing the
application to be responsive when long redraws occur.
• Multiple threads can be concurrently executed if there are multiple CPUs
in the system thus speeding up the execution of the program.
• Complex simulations can be carried out efficiently by assigning a separate
thread to each simulation entity.
• Important events can be handled efficiently by assigning those to a high
priority thread.

Note that each thread can be in one of three possible states, i.e., it could be in
running state where it has the attention of the CPU, or it could be blocked if it is waiting
on a resource, or it could be in ready state residing in a priority queue. In a multi-
processor computer, many threads can be in the running state depending upon the number
of CPUs.

CPU 1
T4

Running
CPU n
T13

Blocked

T2, T6, T8, T9


Ready
T3
T5
Ready Queue T1
(priority Q) T7

The operating system is continuously managing threads, moving them from


running to blocked, or ready state, then picking the next ready thread to give to the CPU
and changing its state to running. If the time slice (typically one millisecond) for a thread
expires while it is in the running state, it is moved to the ready queue. If, however, the
2

thread needs to wait on a resource (such as I/O, or some data arrival) before its time slice
expires, it is moved to the blocked queue, where it will reside until the resource becomes
available.
In windows, each process is assigned a single thread of execution by default. Each
thread gets its own stack space but shares the heap space and the global variables with
other threads in the process as shown below.

Global Variables Global Variables

Heap space Heap space

Environmental Environmental strings


strings

Thread Stack Thread Stack Thread Stack Thread Stack

Thread Thread 1 Thread 2 Thread 3

A Process with One Thread A Process with Three Threads

The Windows operating system provides a CreateThread API function which you
can call to start a thread function. Its prototype looks like.
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES pSecurityAttributes,
DWORD stackSize,
LPTHREAD_START_ROUTINE pStartAddr,
LPVOID pThreadParm,
DWORD createFlags,
LPDWORD pThreadID)

As you can see from the above prototype, the CreateThread function starts a function as a
new thread (indicated by the LPTHREAD_START_ROUTINE parameter) and passes a
single LPVOID type parameter to this function. The windows API provides extensive set
of functions for thread creation, destruction, synchronization and signaling. However
using the MFC library to create and manage threads makes it much easier as there is a
single class called “CWinThread" that you need to learn.
3

MFC Threads:
MFC has two kinds of threads, worker threads and user interface threads.
Majority of the time, we create worker threads to divide the computation into different
threads. MFC provides two global functions AfxBeginThread and AfxEndThread to create
and terminate threads. A thread is written as a simple function that takes one parameter of
LPVOID type and has a UINT return type. AfxBeginThread calls this function and has
the thread function name and the parameter to be given to the thread function as its
parameters. AfxBeginThread creates one object of the CWinThread class on the heap and
returns a pointer to it. CWinThread holds a handle to the created thread through its
m_hThread data member. It is through this CWinThread object that we can control the
thread priority and its termination etc.. Note that AfxBeginThread calls the CreateThread
API function to create a thread.

Example: Create an SDI application called SDIThread. Add a simple function called
MyBeep which will beep every two seconds. Invoke the thread through the
OnInitialUpdate function that you can write by first going through the class wizard.
//-----------Thread function----------------
UINT MyBeep(LPVOID pParam)
{
while(1) {
Beep(100,100);
Sleep(2000); // block for 2 seconds
}
return 0;
}

void CSDIThreadView::OnInitialUpdate()
{
CView::OnInitialUpdate();

// TODO: Add your specialized code here and/or call the base class
AfxBeginThread(MyBeep,NULL);
}

You can modify the code to pass the interval parameter as shown below.
//-----------Thread function----------------
UINT MyBeep(LPVOID pParam)
{
int * duration = (int *) (pParam); // need to type cast it as pParam is LPVOID
while(1) {
Beep(100,100);
Sleep(*duration); // block for 2 seconds
}
return 0;
}

int interval;
void CSDIThreadView::OnInitialUpdate()
{
CView::OnInitialUpdate();
4

// TODO: Add your specialized code here and/or call the base class
interval = 2000;
AfxBeginThread(MyBeep,&interval);
}

Thread Termination:
A thread can terminate in one of three ways.
1. The thread functions ends.
2. The program calls AfxEndThread.
3. The application terminates.

In the first two scenarios, the destructor for the CWinThread object is called and so there
are no memory leaks (recall that the CWinThread object is created on the heap by the
AfxBeginThread function). However, in the third scenario, the program will have
memory leaks as the CWinThread object is not deleted from the stack. Thus in our
previous example where the thread function “MyBeep()” is running in an infinite loop,
there is a memory leak when the program is terminated.
One possibility is to provide a loop variable in the thread function which may
terminate the function when the program is ending. Modify the thread function to as
shown below. You will need to write the WM_DESTROY handler in the view class by
using the class wizard.

int interval; // global


BOOL b_terminate = FALSE; // global
CWinThread * pThread; // global
//-----------Thread function----------------
UINT MyBeep(LPVOID pParam)
{
int * duration = (int *) (pParam);
while(!b_terminate) {
Beep(1000,1000); // frequency, duration
Sleep(*duration); // block for duration miliseconds
}
return 0;
}

void CSDIThreadView::OnInitialUpdate()
{
CView::OnInitialUpdate();

// TODO: Add your specialized code here and/or call the base class
interval = 2000;
pThread = AfxBeginThread(MyBeep,&interval);
}

void CSDIThreadView::OnDestroy()
{
CView::OnDestroy();

// TODO: Add your message handler code here


b_terminate = TRUE;
5

Now as you can see that when the program is terminated, WM_DESTROY handler sets
b_terminate to true which can then potentially terminate the MyBeep thread function.
However, this program will still have memory leaks in the case when the main thread
gets done before the MyBeep thread. To solve this problem, the only solution that is
guaranteed to work is to have the main program wait for the thread to finish in the
WM_DESTROY handler. Modify the code to as shown below.

int interval; // global


BOOL b_terminate = FALSE; // global
CWinThread * pThread; // global
//-----------Thread function----------------
UINT MyBeep(LPVOID pParam)
{
int * duration = (int *) (pParam);
while(!b_terminate) {
Beep(1000,1000); // frequency, duration
Sleep(*duration); // block for duration miliseconds
}
return 0;
}

void CSDIThreadView::OnInitialUpdate()
{
CView::OnInitialUpdate();

// TODO: Add your specialized code here and/or call the base class
interval = 2000;
pThread = AfxBeginThread(MyBeep,&interval);
pThread->m_bAutoDelete = FALSE; // CWinThread object will not be destroyed
} // automatically if thread function ends

void CSDIThreadView::OnDestroy()
{
CView::OnDestroy();

// TODO: Add your message handler code here


b_terminate = TRUE;
WaitForSingleObject(pThread->m_hThread,INFINITE); // wait for thread to terminate
delete pThread;
}

The above code is very typical of those situations where the thread function runs in a long
loop, and the main thread needs to wait on the thread function to finish first, so that there
are no memory leaks.

Thread Priorities:
6

Each thread in Windows can be given a priority from 1 to 31 (with 31 being the
highest priority). The application is given a priority class when it is started. Here is a list
of the different priority classes:
Class Base Priority
IDLE_PRIORITY_CLASS 4
NORMAL_PRIORITY_CLASS foreground: 9 background: 7
HIGH_PRIORITY_CLASS 13
REALTIME_PRIORITY_CLASS 24

By calling the CWinThread member function “SetThreadPriority”, you can change the
priority of a thread within its class. The SetThreadPriority takes one of the following
values.
THREAD_PRIORITY_LOWEST -2 (subtracts 2 from current priority)
THREAD_PRIORITY_BELOW_NORMAL -1
THREAD_PRIORITY_NORMAL 0
THREAD_PRIORITY_ABOVE_NORMAL +1
THREAD_PRIORITY_HIGHEST +2

For example, if a foreground thread’s priority is changed by calling:


SetThreadPriority(THREAD_PRIORITY_BELOW_NORMAL), then its priority will
become 8 (i.e., 9-1).

One of the important rules to remember is not to assign a continuously running thread
function a high priority. This is because when its time slice expires and it goes back to the
ready queue, it will be picked again by the OS scheduler as its priority is higher than
other threads. Only occasionally the random scheduling built into the scheduler may pick
another thread.
The CWinThread class also has member functions to suspend or resume a thread.

Example: Create an MDI application called dots2.


Add the following code to the view class header file:
// Attributes
public:
CDots2Doc* GetDocument();

BOOL bKill;
CWinThread * pThread1;
CWinThread * pThread2;

Add the following code (OnInitialUpdate, WM_DESTROY handlers and two thread
functions) to the dots2view.cpp file. Note that the first thread function draws a blue dot
randomly in the client area while the second thread function draws a red dot. Also note
that the OnInitialUpdate function, sets the priority of the second thread (red thread) to
two less than the normal priority.

void CDots2View::OnInitialUpdate()
{
7

CView::OnInitialUpdate();
// TODO: Add your specialized code here and/or call the base class
bKill = FALSE;
AfxMessageBox(" On initial update called ");
pThread1 = AfxBeginThread(DotThread1, this);
pThread1 -> m_bAutoDelete = FALSE;
pThread2 = AfxBeginThread(DotThread2, this);
pThread2 -> m_bAutoDelete = FALSE;
pThread2->SetThreadPriority(THREAD_PRIORITY_LOWEST);
}
void CDots2View::OnDestroy()
{
CView::OnDestroy();
// TODO: Add your message handler code here
bKill = TRUE;
WaitForSingleObject(pThread1->m_hThread, INFINITE);
delete pThread1;
WaitForSingleObject(pThread2->m_hThread, INFINITE);
delete pThread2;
}

// Thread 1 function
UINT DotThread1 (LPVOID pParam)
{
CDots2View * view = (CDots2View *) pParam;
CRect r;
srand(GetTickCount());
while (!view->bKill)
{
CClientDC dc(view);
view->GetClientRect(&r);
int i = rand() % r.Width();
int j = rand() % r.Height();
dc.SetPixel(i,j,RGB(0,0,255));
}
return 0;
}

// Thread 2 function
UINT DotThread2 (LPVOID pParam)
{
CDots2View * view = (CDots2View *) pParam;
CRect r;
srand(GetTickCount());
while (!view->bKill)
{
CClientDC dc(view);
view->GetClientRect(&r);
int i = rand() % r.Width();
int j = rand() % r.Height();
dc.SetPixel(i,j,RGB(255,0,0));
}
return 0;
}
8

In the IDR_DOTS2TYPE menu resource, add a menu item called Thread. Underneath it,
create two menu items, Suspend 1, and Resume 1 (which will allow us to either suspend
or resume the first thread).

In the dots2view.cpp file, add the code for the Suspend 1 (ID_THREAD_SUSPEND1)
and Resume 1 (ID_THREAD_RESUME1) menu handlers.

void CDots2View::OnThreadSuspend1()
{
// TODO: Add your command handler code here
int m = pThread1->SuspendThread();
CString s1;
s1.Format(" count = %d",m);
AfxMessageBox(s1);
}

void CDots2View::OnThreadResume1()
{
// TODO: Add your command handler code here
pThread1->ResumeThread();
}

Build and execute the program. Experiment with suspending and resuming the first
thread. You will notice that in the beginning, the client area is filled with blue dots as its
thread has a higher priority. However, as you suspend the first thread, the client area
starts to fill with red dots.

As mentioned earlier, when there is a significant amount of drawing code (from


execution time point of view) in the client area, the user interface is blocked out when the
9

drawing is taking place. However, the application can be made more responsive by
running the drawing code in a separate thread from the main thread.

Example:
Create another MDI application called mandel. This will do a fractal image
drawing (which can be time consuming if the area is large).
Type the following code in the mandelview.h file. (It has some extra code related to a dll
that I wanted to show later when discussing creation of dlls).

UINT mydraw(LPVOID pParam);

typedef UINT (DRAWIT)(LPVOID pParam);


extern "C" __declspec(dllimport) UINT drawitdll(LPVOID pParam);

struct dllparam {
// CClientDC *pdc;
HDC * pDC;
CRect r1;
};

//------- provideed by the testdll.dll now


const int NUM_ITERATIONS=64;

const double left = -1.5;


const double right = 1.25;
const double top = -1.25;
const double bottom = 1.25;

typedef struct
{
double real;
double imag;
} complex;

UINT drawit(LPVOID pParam);


void Initcolors();

class CMandelView : public CView


{
protected: // create from serialization only
CMandelView();
DECLARE_DYNCREATE(CMandelView)

// Attributes
public:
CMandelDoc* GetDocument();
CWinThread * pmythread;
DRAWIT * pFunction; // used in the dll version
…..
…..
10

Type the following code (some are event handlers that you will need to add through the
class wizard) in the mandleview.cpp file. Add a menu item called “Draw” under the
window menu. Also add a menu called Drawing Thread. Underneath it, add suspend and
resume menu items.
DRAWIT * pF;

// -------- these are provided by the testdll.dll --


DWORD colors[64];

void Initcolors()
{
// TODO: add construction code here
WORD x;
BYTE red=0, green=0, blue=0;
for (x = 0; x < 64; x++)
{
colors[x] = RGB(red, green, blue);
if (!(red+= 64))
if (!(green+= 64))
blue += 64;
}
colors[63] = RGB(255,255,255);
}

CMandelView::CMandelView()
{
// TODO: add construction code here
::Initcolors();// now dll is going to do this
/* HINSTANCE hInstance; // load a DLL
VERIFY (hInstance = ::LoadLibrary("c:\\windows\\system\\testdll.dll"));
VERIFY (pFunction = (DRAWIT*)::GetProcAddress(hInstance,"drawitdll"));
pF = pFunction;
AfxMessageBox("dll loaded"); */
}

void CMandelView::OnInitialUpdate()
{
// CView::OnInitialUpdate();

// TODO: Add your specialized code here and/or call the base class

void CMandelView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint)


{
// TODO: Add your specialized code here and/or call the base class
CClientDC dc(this);
CRect r;
GetClientRect(&r);
dllparam d1;
d1.pDC = &(dc.m_hDC);
d1.r1 = r;
// (*pF)(&d1);
11

// pmythread = AfxBeginThread(drawit,this);
::drawit(this); //drawing without a thread, uncomment to see its effect
// pmythread = AfxBeginThread(pF,&d1);
}

UINT mydraw(LPVOID pParam)


{
dllparam * d1 = (dllparam *) pParam;
(*pF)(d1);
return 0;
}

UINT drawit(LPVOID pParam) {


CRect r;
double xstep, ystep;
double x, y;
int i, j;
WORD iter;
complex k;
complex z;
double real, imag, spread;

CView * pview = (CView *) pParam;

CClientDC dc(pview);

pview->GetClientRect(&r);

ystep = (double) (bottom - top) /r.Height();


xstep = (double) (right - left) / r.Width();

for (y = top, j = 0; y <= bottom; y+=ystep, j++)


{
for (x=left,i=0; x<= right; x+= xstep, i++)
{
k.real = x;
k.imag = y;

z.real = 0.0; z.imag = 0.0;

for (iter = 0; iter < NUM_ITERATIONS - 1; iter++)


{
real = z.real + k.real;
imag = z.imag + k.imag;
z.real = real * real - imag * imag;
z.imag = 2 * real * imag;
spread = z.real * z.real + z.imag * z.imag;
if (spread > 4.0)
break;
}
dc.SetPixel(i,j,colors[iter]);
}
}
return 0;
12

void CMandelView::OnThreadSuspend()
{
// TODO: Add your command handler code here
if(pmythread) pmythread->SuspendThread();
}

void CMandelView::OnThreadResume()
{
// TODO: Add your command handler code here
if (pmythread) pmythread->ResumeThread();
}

void CMandelView::OnWindowDraw() // event handler for Window->Draw menu item


{
// TODO: Add your command handler code here
GetDocument()->UpdateAllViews(0,NULL,0);
}

If you run the program, it will create a fractal image as shown below:

As the above program is drawing (once you select Draw from the window menu), you
will notice that you cannot select anything from the menus because it is running as a
single threaded application. However, now if you change the code in the OnUpdate
function to run the drawing code in a separate thread, to as shown below, you will see
that the user interface is responsive while the fractal image is being drawn.
void CMandelView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint)
{
// TODO: Add your specialized code here and/or call the base class
13

CClientDC dc(this);
CRect r;
GetClientRect(&r);
dllparam d1;
d1.pDC = &(dc.m_hDC);
d1.r1 = r;
// (*pF)(&d1);
pmythread = AfxBeginThread(drawit,this);
// ::drawit(this); //drawing without a thread, uncomment to see its effect
// pmythread = AfxBeginThread(pF,&d1);
}

Thread Synchronization:
Since different threads in an application can share the global variables, this
capability can lead to data consistency problems. This can arise if one thread is trying to
modify a data structure, but before it can completely modify it, its time slice expires and
another thread tries to read the data. This problem is also referred to as thread
synchronization problem. There are several mechanisms built into an OS to solve this
problem e.g., semaphores, mutexes, critical sections, and events. Here is a simple
example demonstrating the use of a semaphore.
Critical section allows only one thread to enter it at a given point in time, while a
semaphore defines a counter internally to keep track of how many threads can be granted
access. If the count reaches zero, the thread trying to access is blocked. Mutexes allow
safe sharing of cross-process resources such as files. Events can be used in a producer
consumer type of environment where the consumer is signaled when the producing thread
has the data ready.
Example: Create a win32 console type application. Make sure to include the MFC
library as a shared dll (by going through project->settings menu).
The following example has two threads that access a global structure called time
which is initialized to 3 hours, 59 minutes and 59 seconds. The first threads increments
the seconds count and the second thread tries to read the current time. If the first thread’s
time slice expires after it has incremented the seconds and minutes (but before it can
change the hours to 4), the CheckTime thread function will read the time to be 3:00:00
which is quite wrong. We can make the first thread block before it changes the hours to 4
by having it sleep for a small time.

//------CS.cpp-------
// Example showing how to use a Critical Scetion
// for synchronization in multithreading

#include <afxwin.h> // for using AfxBeginThread


#include <iostream.h>
#include <afxmt.h> // for MFC synchronization classes

struct Time { // global structure


int hours;
int minutes;
int seconds;
} time1;
14

UINT IncrementTime(LPVOID p1); // increments time


UINT CheckTime(LPVOID p1);

int main() {
time1.hours = 3; time1.minutes=59; time1.seconds=59;
cout << "Time Before Increment = " << time1.hours <<":" <<
time1.minutes << ":" << time1.seconds << endl;
cout << "starting Increment and CheckTime threads" << endl;
AfxBeginThread(IncrementTime,NULL);
AfxBeginThread(CheckTime,NULL);
Sleep(2000);

return 0;
}

UINT IncrementTime(LPVOID p1)


{
time1.seconds ++;
if (time1.seconds == 60) {
time1.seconds = 0;
time1.minutes ++;
if (time1.minutes == 60) {
time1.minutes = 0;
Sleep(1000); // try without this line
// now it will produce wrong results
time1.hours ++;
if (time1.hours == 13)
time1.hours = 1;
}
}
return 0; }
UINT CheckTime(LPVOID p1)
{
cout << "Current Time = " << time1.hours <<":" <<
time1.minutes << ":" << time1.seconds << endl;
return 0;

The output of the program will look like:


Time Before Increment = 3:59:59
Starting Increment and CheckTime Threads
Current Time = 3:0:0

We can solve the above problem by using synchronization mechanisms such as


semaphores, critical sections or mutexes. If you modify the above program, as: (the
modifications are shown in bold)
//------CS.cpp-------
// Example showing how to use a Critical Scetion
// for synchronization in multithreading

#include <afxwin.h>
#include <iostream.h>
#include <afxmt.h>

struct Time {
15

int hours;
int minutes;
int seconds;
} time1;
//---add the following after testing the problem
CRITICAL_SECTION gCS;
UINT IncrementTime(LPVOID p1); // increments time
UINT CheckTime(LPVOID p1);

int main() {
::InitializeCriticalSection(&gCS);
// you must initialize the CS before using it
time1.hours = 3; time1.minutes=59; time1.seconds=59;
cout << "Time Before Increment = " << time1.hours <<":" <<
time1.minutes << ":" << time1.seconds << endl;
cout << "starting Increment and CheckTime threads" << endl;
AfxBeginThread(IncrementTime,NULL);
AfxBeginThread(CheckTime,NULL);
Sleep(2000);
::DeleteCriticalSection(&gCS);
return 0;
}

UINT IncrementTime(LPVOID p1)


{
// add the following after testing the problem
::EnterCriticalSection(&gCS);
time1.seconds ++;
if (time1.seconds == 60) {
time1.seconds = 0;
time1.minutes ++;
if (time1.minutes == 60) {
time1.minutes = 0;
Sleep(1000); // try without this line
// now it will produce wrong results
time1.hours ++;
if (time1.hours == 13)
time1.hours = 1;
}
}
::LeaveCriticalSection(&gCS);
return 0;
}

UINT CheckTime(LPVOID p1)


{
::EnterCriticalSection(&gCS);
cout << "Current Time = " << time1.hours <<":" <<
time1.minutes << ":" << time1.seconds << endl;
::LeaveCriticalSection(&gCS);
return 0;
}
16

Making Classes Thread Safe:


Even though a thread function is a simple function (i.e., not a member function of
a class), it can create objects of classes and call their member functions. Since the class’s
member functions often access data members (which act as global variables to all
member functions of a class), and the different member functions can be potentially
invoked through different threads, we have the same synchronization problem of
consistency of global shared data as discussed earlier. Thus the best way to make a class
thread safe, meaning that it can be safely invoked in a thread is to protect each of its
member functions that accesses the data members through synchronization primitives
such as critical sections, mutexes or semaphores.

Example:
Here is how you will make class x thread safe.
class x {
int a, b;
CRITICAL_SECTION gCS;
public:
x(int p, int q) {
::InitializeCriticalSection(&gCS);
::EnterCriticalSection(&gCS);
a = p; b = q;
::LeaveCriticalSection(&gCS);
}
int get_a() {
::EnterCriticalSection(&gCS);
return a;
::LeaveCriticalSection(&gCS);
}
void set_a(int m) {
::EnterCriticalSection(&gCS);
a = m;
::LeaveCriticalSection(&gCS);
}
int get_b() {
::EnterCriticalSection(&gCS);
return b;
::LeaveCriticalSection(&gCS);
}
void set_b(int m) {
::EnterCriticalSection(&gCS);
b = m;
::LeaveCriticalSection(&gCS);
}

virtual int sum_all() {


::EnterCriticalSection(&gCS);
return a+b;
::LeaveCriticalSection(&gCS);
}
};
17

Using MFC for Thread Synchronization: MFC provides a few easy to use
synchronization classes for creating and using semaphores, critical sections, mutexes and
events.
Example: Create an SDI application called thread2. It will have two threads in it, one
will try to modify a global data (x, y variables) and the other thread will try to read their
values. The initial values of x and y are set to 2. The first thread increases the value of x
by 1, then it sleeps for 5 seconds, and then tries to increase y by 1. As you can see, after
increasing x, the first thread’s time slice will expire and when the second thread reads it,
it will find the value to be x = 3 and y = 2. Let us pretend that both x and y needed to be
incremented before they can be read by another thread. We will solve this problem by
using semaphores (MFC classes CSingleLock and CSemaphore).
Add a menu item called “Synchronize threads” to the menu resource. Here is the partial
code for the thread2view.cpp file.

UINT thread1(LPVOID);
UINT thread2(LPVOID);

// Globals
int x = 2, y = 2;


UINT thread1(LPVOID p1)
{
x++;
Sleep(5000); // sleep for 5 seconds
y++;
return 0;
}

UINT thread2(LPVOID p1) // display context of x and y


{
CThread2View* view = (CThread2View*) p1; // type cast LPVOID to
// to class view type pointer
CClientDC dc(view); // create Device context object "dc" of type CClientDC
CString sx;
sx.Format("x = %d, y = %d",x,y);
dc.TextOut(0,0,sx);
return 0;
}

void CThread2View::OnSyncThreads() // Event or Message handler


// to start 2 threads
{
// TODO: Add your command handler code here
AfxBeginThread(thread1, NULL);
AfxBeginThread(thread2, this);

If you run the above program, and choose synchronize threads from the menu, you will
get an output of x=3, y=2 as shown below. Remember, we want the output to be 3 and 3.
18

Now let us add the synchronization code. We will use a CSingleLock class and
CSemaphore class for this. Typically, we create a global CSemaphore object (or a data
member in a class). Then wherever we are reading or modifying shared data, we pass the
semaphore object to the CSingleLock object constructor, and use the Lock and UnLock
methods of the CSingleLock class to protect our shared data. Here are the modifications
needed to implement correct synchronization in our previous example. Note that you
need to include <afxmt.h> file in order to use MFC synchronization classes.
#include <afxmt.h>
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

UINT thread1(LPVOID);
UINT thread2(LPVOID);

// Globals
int x = 2, y = 2;
CSemaphore sema; // CSemaphore enables syncrozize threads


UINT thread1(LPVOID p1)
{
CSingleLock semlock(&sema);
semlock.Lock();
x++;
Sleep(5000); // sleep for 5 seconds
y++;
semlock.Unlock();
return 0;
}

UINT thread2(LPVOID p1) // display context of x and y


19

{
CThread2View* view = (CThread2View*) p1; // type cast LPVOID to
// to class view type pointer
CClientDC dc(view); // create Device context object "dc" of type CClientDC
CString sx;

CSingleLock semlock(&sema);
semlock.Lock();
sx.Format("x = %d, y = %d",x,y);
semlock.Unlock();
dc.TextOut(0,0,sx);
return 0;
}

void CThread2View::OnSyncThreads() // Event or Message handler


// to start 2 threads
{
// TODO: Add your command handler code here
AfxBeginThread(thread1, NULL);
AfxBeginThread(thread2, this);

Build and run the program and you will see that the second thread blocks because of the
semaphore lock (even though the first thread goes to sleep for 5 seconds after
incrementing x). When you choose the menu selection “Synchronize threads”, it will take
slightly more than 5 seconds for the output to appear, but the output will be correct i.e., 3
and 3.

Example of Events in Multi-threading: Often we run into a situation where one


thread may be producing some data while the other thread should not start to consume
data until it is ready. MFC provides a CEvent class for this purpose. It has two important
member functions called SetEvent and ResetEvent. The thread that wants to wait on an
event will create a CSingleLock object and tie it to the CEvent data member using its
constructor. Then it will call the Lock method of the CSingleLock class to wait on the
event. Another thread, then might call the SetEvent through the CEvent data member
object to signal the blocked thread.
20

To understand this a little better, create an SDI application called ThreadEvent.


Add a data member to the view class as shown below:

// ThreadEventView.h : interface of the CThreadEventView class


//
/////////////////////////////////////////////////////////////////////////////

#if !
defined(AFX_THREADEVENTVIEW_H__7B78B767_AEC7_4080_8C47_7924680B8750__INCLUDE
D_)
#define
AFX_THREADEVENTVIEW_H__7B78B767_AEC7_4080_8C47_7924680B8750__INCLUDED_

#if _MSC_VER > 1000


#pragma once
#endif // _MSC_VER > 1000

#include <afxmt.h> // added by Ausif


class CThreadEventView : public CView
{
protected: // create from serialization only
CThreadEventView();
DECLARE_DYNCREATE(CThreadEventView)

// Attributes
public:
CThreadEventDoc* GetDocument();
CEvent Event; // added by Ausif

Add a menu item called “Start Thread Event”. Add the event handler for
WM_LBUTTONDOWN using the class wizard, and also the menu event handler. The
ultimate view class cpp file will look like: (only the last part of view file is shown).

UINT ConsumerEvent(LPVOID p1) // thread function


{
CThreadEventView * pView = (CThreadEventView *) p1;
CClientDC dc(pView);
dc.TextOut(0,0,"Waiting for Thread event, click to generate thread event");
CSingleLock cs(&(pView->Event));
cs.Lock(); // wait for thread event
dc.TextOut(0,100,"Thread Event Received, proceeding on");
cs.Unlock();
return 0;
}

void CThreadEventView::OnThreadStart() // menu handler for Start Thread Event


{
// TODO: Add your command handler code here
AfxBeginThread(ConsumerEvent,this);

}
21

void CThreadEventView::OnLButtonDown(UINT nFlags, CPoint point)


// WM_LBUTTONDOWN handler
{
// TODO: Add your message handler code here and/or call default
Event.SetEvent() ; //fire an event
CView::OnLButtonDown(nFlags, point);
}

Run the program, click on the menu item “Start Thread Event”. Then click any where in
the client area to signal the event to the blocked thread called “ConsumerEvent”.

Vous aimerez peut-être aussi