Introduction
Once you start doing multi-threaded programs, the first thing you know, you
are faced with the problem of multiple threads accessing the same resource.
Sometimes this can result in depressing errors. Like when two threads try to
open the same file. Or when one thread is writing to a file and the other thread
is trying to truncate it. You might get program crashes which is bad, and you
might get a corrupted file, which is considerably worse.
The .NET framework gives us the Monitor
class as one solution,
but there are other methods too like settable events. This article will cover
the usage of the Monitor
class for thread synchronization. But first, I'd
like to try and simulate a thread-synchronization problem. Take a look at the
below program. There are two threads. Both of them increment the same int
resource x. I've simulated the processing by using two for
loops and a
Sleep
. So
basically we expect to see 20 numbers [from 0 to 19 in that order] displayed on
screen
Program Listing
#using <mscorlib.dll>
using namespace System;
using namespace System::Threading;
__gc class AddThread
{
public:
int x;
void Add1();
AddThread();
Thread *t1,*t2;
};
int wmain(void)
{
AddThread *a=new AddThread();
return 0;
}
AddThread::AddThread()
{
x=0;
t1=new Thread(new ThreadStart(this,&AddThread::Add1));
t2=new Thread(new ThreadStart(this,&AddThread::Add1));
t1->Name="Thread 1";
t2->Name="Thread 2";
t1->Start();
t2->Start();
}
void AddThread::Add1()
{
for (int j=0;j<10;j++)
{
for (int t=0;t<5000;t++)
x++;
Thread::Sleep(5);
for (int t=0;t<5000;t++)
x--;
Console::WriteLine("{0}.....{1} has incremented x",
__box(x++),Thread::CurrentThread->Name);
}
}
Now compile and run that program. You might have expected to see numbers 0 to
19 shown on screen. Well, this is what I got. I presume you'll get something
similar depending on your processor's speed and its current load.
Output
D:\MyProjs\mcppthreadsync01\Debug>mcppthreadsync01.exe
5000.....Thread 1 has incremented x
5001.....Thread 2 has incremented x
5002.....Thread 1 has incremented x
5003.....Thread 2 has incremented x
5004.....Thread 1 has incremented x
5005.....Thread 2 has incremented x
5006.....Thread 1 has incremented x
5007.....Thread 2 has incremented x
5008.....Thread 1 has incremented x
5009.....Thread 2 has incremented x
5010.....Thread 1 has incremented x
5011.....Thread 2 has incremented x
5012.....Thread 2 has incremented x
5013.....Thread 1 has incremented x
5014.....Thread 2 has incremented x
15.....Thread 1 has incremented x
5016.....Thread 2 has incremented x
5017.....Thread 1 has incremented x
5018.....Thread 2 has incremented x
19.....Thread 1 has incremented x
D:\MyProjs\mcppthreadsync01\Debug>
Hmmm. Not what we wanted. Not at all! The whole problem is that one of the
threads is doing something with x, just as the other thread is accessing it. So,
how do we prevent that?
Well, that's where the Monitor
class comes into play. We can use the
Monitor
class to lock a resource so that no other thread can access it
and then release it after use.
I have modified the Add1
member function as follows
void AddThread::Add1()
{
for (int j=0;j<10;j++)
{
Monitor::Enter(this);
for (int t=0;t<5000;t++)
x++;
Thread::Sleep(5);
for (int t=0;t<5000;t++)
x--;
Console::WriteLine("{0}.....{1} has incremented x",
__box(x++),Thread::CurrentThread->Name);
Monitor::Exit(this);
}
}
Now compile and run the program.
Output
D:\MyProjs\mcppthreadsync01\Debug>mcppthreadsync01.exe
0.....Thread 1 has incremented x
1.....Thread 2 has incremented x
2.....Thread 1 has incremented x
3.....Thread 2 has incremented x
4.....Thread 1 has incremented x
5.....Thread 2 has incremented x
6.....Thread 1 has incremented x
7.....Thread 2 has incremented x
8.....Thread 1 has incremented x
9.....Thread 2 has incremented x
10.....Thread 1 has incremented x
11.....Thread 2 has incremented x
12.....Thread 1 has incremented x
13.....Thread 2 has incremented x
14.....Thread 1 has incremented x
15.....Thread 2 has incremented x
16.....Thread 1 has incremented x
17.....Thread 2 has incremented x
18.....Thread 1 has incremented x
19.....Thread 2 has incremented x
D:\MyProjs\mcppthreadsync01\Debug>
Ah! That's what we wanted. Hmm, let's take a look at how that came about. We
added the following two lines.
Monitor::Enter(this);
and
Monitor::Exit(this);
Enter
will block [means it just waits there] if some other thread has
locked the resource [in our case, we pass the this
pointer]. If the
object is free, then Enter
will obtain a monitor lock on that object.
Exit
will release the monitor lock on that resource. They are both
static
member functions of the Monitor
class.
From the same thread you can invoke Enter
more than once and it won't
block; but you must make sure that there are as many Exit
calls as there
were Enter
calls.
In some cases blocking would be undesirable. In that case, you can use an
alternate call called TryEnter
. It has three overloads. The overload we
are interested in is
public: static bool TryEnter(Object* obj);
This won't block but will return true
if the lock was obtained
and false
if the resource was already locked. There are other
overloads for TryEnter
which allow us to block for a specified time
interval. You can look up Monitor::TryEnter
in the
.NET Framework SDK documentation.
Let's say we want Thread 2 to finish the first six numbers before we want
thread 1 to start its work. Here we can make use of the Wait
and
Pulse
member functions.
I have modified the Add1
function again :-
void AddThread::Add1()
{
for (int j=0;j<10;j++)
{
Monitor::Enter(this);
if(Thread::CurrentThread->Name->Equals("Thread 1"))
{
if(x<5)
Monitor::Wait(this);
}
else
{
if(x>4)
Monitor::Pulse(this);
}
for (int t=0;t<5000;t++)
x++;
Thread::Sleep(5);
for (int t=0;t<5000;t++)
x--;
Console::WriteLine("{0}.....{1} has incremented x",
__box(x++),Thread::CurrentThread->Name);
Monitor::Exit(this);
}
}
What Wait
does is that it frees the monitor but indicates to the CLR
that it expects to get the monitor back when its free again. And Pulse
indicates to the CLR that a change in state has occurred which might free one of
the waiting threads. The CLR keeps track of all waiting threads which it frees
in the order in which they invoked Wait
.
Now compile and run the program. You'll get this :-
D:\MyProjs\mcppthreadsync01\Debug>mcppthreadsync01.exe
0.....Thread 2 has incremented x
1.....Thread 2 has incremented x
2.....Thread 2 has incremented x
3.....Thread 2 has incremented x
4.....Thread 2 has incremented x
5.....Thread 2 has incremented x
6.....Thread 1 has incremented x
7.....Thread 2 has incremented x
8.....Thread 1 has incremented x
9.....Thread 2 has incremented x
10.....Thread 1 has incremented x
11.....Thread 2 has incremented x
12.....Thread 1 has incremented x
13.....Thread 2 has incremented x
14.....Thread 1 has incremented x
15.....Thread 1 has incremented x
16.....Thread 1 has incremented x
17.....Thread 1 has incremented x
18.....Thread 1 has incremented x
19.....Thread 1 has incremented x
There is also a PulseAll
function that notifies all waiting threads.
The thread that invoked PulseAll
releases the lock. Remember that
Pulse
, PulseAll
and Wait
must only be invoked from within a
synchronized block of code.
Thank You