Click here to Skip to main content
11,575,925 members (57,800 online)
Click here to Skip to main content

Thread Sychronization using monitors

, 31 Oct 2001 CPOL 66.6K 20
Rate this:
Please Sign up or sign in to vote.
Introduction to using the Monitor class for accessing shared resources from multiple threads

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++;

        //using Sleep to simulate a real-case scenario
        //where heavy time-consuming calculations might be done		
        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);    //added this line	
        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);    //added this line

    }
}

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);				
		
        //<newly_added_block>
        if(Thread::CurrentThread->Name->Equals("Thread 1"))
        {
            if(x<5)
                Monitor::Wait(this);
        }
        else
        {
            if(x>4)
                Monitor::Pulse(this);
        }
        //</newly_added_block>

        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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Nish Nishant
United States United States
Nish Nishant is a Software Architect/Consultant based out of Columbus, Ohio. He has over 15 years of software industry experience in various roles including Lead Software Architect, Principal Software Engineer, and Product Manager. Nish is a recipient of the annual Microsoft Visual C++ MVP Award since 2002 (13 consecutive awards as of 2014).

Nish is an industry acknowledged expert in the Microsoft technology stack. He authored
C++/CLI in Action for Manning Publications in 2005, and had previously co-authored
Extending MFC Applications with the .NET Framework for Addison Wesley in 2003. In addition, he has over 140 published technology articles on CodeProject.com and another 250+ blog articles on his
WordPress blog. Nish is vastly experienced in team management, mentoring teams, and directing all stages of software development.

Contact Nish : You can reach Nish on his google email id voidnish.

Website and Blog

You may also be interested in...

Comments and Discussions

 
Questionblocking with this Pin
njupas2-Nov-06 4:13
membernjupas2-Nov-06 4:13 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.150603.1 | Last Updated 1 Nov 2001
Article Copyright 2001 by Nish Nishant
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid