By using this site, you agree to our updated Privacy Policy and our Terms of Use. Manage your Cookies Settings.
464,556 Members | 970 Online
Bytes IT Community
+ Ask a Question
Need help? Post your question and get tips & solutions from a community of 464,556 IT Pros & Developers. It's quick & easy.

Conceptual Problem with Monitor.Enter and Monitor.Exit

P: n/a
Dear Members,
I have a problem in the concepts of Monit.Enter and Monitor.Exit
(Before everything I should say that I know how to solve this problem
by

using Monitor.Wait and I do not need a solution. But this question
sticks to

my head as a conceptual problem)
Suppose there are two threads T1, T2 running concurrently:

T1()
{
While(true){
Monit.Enter(MyObjec);
{
....
}
Monitor.Exit(MyObjec);
}
}

T2()
{
While(true){
Monit.Enter(MyObjec);
{
....
}
Monitor.Exit(MyObjec);
}
}
Suppose T1 runs before T2 a little sooner.

After T1 gains the lock on MyObjec, T2 comes in and tries to get
access to

MyObjec too. Since T1 called Monitor.Enter(MyObjec) previously T2 will
be

blocked ( but as what MSDN says, it still remains in ready queue).
After a

while, T1 calls Monitor.Exit(MyObjec). So T2 can gain access to
MyObjec.

But surprisingly it cannot. Because T1 rapidly gains access to MyObjec
again.

It seems that while loop cycles too fast to let T2 gain the lock on
MyObjec. I

am not sure this is the matter of speed but I am sure that there is no
need

to call Monitor.Pulse(MyObjec) to notify T2 because T2 is still in
ready queue

(and Monitor.Pulse(MyObjec) just affects waiting queue).

What is the problem? Is this the matter of speed? Am I wrong about
the

concepts of Monitor methods?

Thanks in advance.
Jun 27 '08 #1
Share this Question
Share on Google+
13 Replies

P: n/a
Are you sure of your test? In the following, T2 wins every time (on my
machine, at least...).

Note that "setupReady" is used to guarantee our initial state - i.e.
T1 gets the lock and releases T2 (then does a short sleep to let T2
catch up), then tries to Enter (blocks), T1 releases the lock (Exit)
and immediately re-obtains it (Enter).

Marc

using System.Threading;
using System;
using System.Diagnostics;
static class Program
{
static void Main()
{
int t1 = 0, t2 = 0;
for (int i = 0; i < 1000; i++)
{
switch (RunTest())
{
case 1: t1++; break;
case 2: t2++; break;
}
}
Console.WriteLine("{0} vs {1}", t1, t2);
Console.ReadKey();
}
class WinnerStub
{
public volatile int Winner = 0;
}
static int RunTest()
{
WinnerStub sync = new WinnerStub();
ManualResetEvent setupReady = new ManualResetEvent(false),
haveAnswer = new ManualResetEvent(false);
ThreadPool.QueueUserWorkItem(delegate
{
Monitor.Enter(sync);
setupReady.Set();
Thread.Sleep(100);
// drop lock...
Monitor.Exit(sync);
Monitor.Enter(sync);
if (sync.Winner == 0)
{
sync.Winner = 1;
haveAnswer.Set();
}
Monitor.Exit(sync);
});
ThreadPool.QueueUserWorkItem(delegate
{
setupReady.WaitOne();
Monitor.Enter(sync);
if (sync.Winner == 0)
{
sync.Winner = 2;
haveAnswer.Set();
}
Monitor.Exit(sync);
});
haveAnswer.WaitOne();
Console.WriteLine("Winner: {0}", sync.Winner);
return sync.Winner;
}

}
Jun 27 '08 #2

P: n/a
(btw, I also tried a "while" setup like yours, but the results were
very even between the threads; so if you can post a short and complete
example that illustrates this?)
Jun 27 '08 #3

P: n/a
On Jun 16, 1:14 pm, Marc Gravell <marc.grav...@gmail.comwrote:
(btw, I also tried a "while" setup like yours, but the results were
very even between the threads; so if you can post a short and complete
example that illustrates this?)
Thank you for your attention. But you repeated what I told. My problem
is exactly what you mentioned: T2 always wins. But as you and I see in
my sample code (similarly in your code) one thread get the access and
then release it.
BUT:
Why the second thread can not get the access after that the first
thread released? As we know the second thread is on ready queue and
waiting eagerly for thread one to release!
Jun 27 '08 #4

P: n/a
Thank you for your attention. But you repeated what I told. My problem
is exactly what you mentioned: T2 always wins.
No; you said the exact opposite:
After a while, T1 calls Monitor.Exit(MyObjec). So T2 can gain access to
MyObjec. But surprisingly it cannot. Because T1 rapidly gains access to
MyObjec again. It seems that while loop cycles too fast to let T2 gain the
lock on MyObjec.
So it sounds like you are saying "T1 always wins".
Why the second thread can not get the access after that the first
thread released?
In my code the second thread (T2) *did* get access after the first
thread (T1) release, 100% of the time.

Hence: the best way to understand the difference is for you to post
something short but *complete* that demostrates what you are seeing.
Threading is one of those areas where the devil is *really* in the
detail.

Marc
Jun 27 '08 #5

P: n/a
On Jun 16, 2:25 pm, Marc Gravell <marc.grav...@gmail.comwrote:
Thank you for your attention. But you repeated what I told. My problem
is exactly what you mentioned: T2 always wins.

No; you said the exact opposite:
After a while, T1 calls Monitor.Exit(MyObjec). So T2 can gain access to
MyObjec. But surprisingly it cannot. Because T1 rapidly gains access to
MyObjec again. It seems that while loop cycles too fast to let T2 gain the
lock on MyObjec.

So it sounds like you are saying "T1 always wins".
Why the second thread can not get the access after that the first
thread released?

In my code the second thread (T2) *did* get access after the first
thread (T1) release, 100% of the time.

Hence: the best way to understand the difference is for you to post
something short but *complete* that demostrates what you are seeing.
Threading is one of those areas where the devil is *really* in the
detail.

Marc
Marc,
You are right and wrong!.
What I said in the first post is what I expected but never happened.
It is the sequence (which after reading MSDN) I expected but never
this ideal situation came true. I am investigating the reason. So:

1- Your result is true and exactly like me.
2- What I posted in the first post was what I designed and what I
expected to happen according to MSDN.
3- What I was expecting never happened at all (like what you
reported).
4- Now I am looking for the reason.

My actual code is not too different comparing with the simple bunch of
code I posted earlier. It is clear and can tell us everything needed.
So I think there is no need to post anything more. But if you insist I
post it.
Thanks again for your attention.

Jun 27 '08 #6

P: n/a
1- Your result is true and exactly like me.

Well, I really believe that we are seeing opposite behaviour.
2 - What I posted in the first post was what I designed and what I
expected to happen according to MSDN.
3- What I was expecting never happened at all (like what you
reported).
And I am saying that my code demonstrates exactly what you expected
from 2 - i.e. the second thread gets the lock. I did not report the
same findings as you in 3; I reported the opposite.

But if you are content...

Marc
Jun 27 '08 #7

P: n/a
On Jun 16, 5:04 pm, Marc Gravell <marc.grav...@gmail.comwrote:
1- Your result is true and exactly like me.

Well, I really believe that we are seeing opposite behaviour.
2 - What I posted in the first post was what I designed and what I
expected to happen according to MSDN.
3- What I was expecting never happened at all (like what you
reported).

And I am saying that my code demonstrates exactly what you expected
from 2 - i.e. the second thread gets the lock. I did not report the
same findings as you in 3; I reported the opposite.

But if you are content...

Marc
The second thread gets the lock but never releases it. Thread one
never gets the lock. Thats the problem.
Despite of which thread we are talking about, one thread always gets
the lock and never releases it. Why?
Jun 27 '08 #8

P: n/a
I think I may have confused you with the code. My example was to
contradict your specific claim that T2 never got the lock. With 2
threads running parallel, timing is important, but it isn't true that
one thread always keeps the lock; for example (going back to the
"while" example) I get pretty balanced results as follows:

T2: 46207
T1: 53794

With the code as below; this is two threads contending over a singel
lock, each incrementing a local counter, with 100k iterations (give-or-
take 1). Individual runs might be less balanced (or more balanced)
depending on what the OS is doing, the number of cores available,
karma, etc - but I would not expect to see 100k vs 0, or 0 vs 100k.

Marc

using System.Threading;
using System;
static class Program
{
static void Main()
{
ThreadPool.QueueUserWorkItem(DoWork, "T1");
ThreadPool.QueueUserWorkItem(DoWork, "T2");
Thread.Sleep(500); // let T1 & T2 get to evt.WaitOne
evt.Set();
Console.ReadKey();
}
static readonly ManualResetEvent evt = new
ManualResetEvent(false);
static readonly object sync = new object();
static int tally = 100000;
static void DoWork(object name)
{
int counter = 0;
evt.WaitOne(); // start both at the same time...
while (true)
{
Monitor.Enter(sync);
counter++;
Monitor.Exit(sync);

if(Interlocked.Decrement(ref tally) <= 0) break;
}
Console.WriteLine("{0}: {1}", name, counter);
}
}
Jun 27 '08 #9

P: n/a
And just in case the extra time for the Interlocked.Decrement was
changing things, I moved that inside the lock - but the behaviour is
still the same; updated "DoWork" follows, typical results:

T2: 53158
T1: 46843

static void DoWork(object name)
{
int counter = 0;
evt.WaitOne(); // start both at the same time...
while (true)
{
Monitor.Enter(sync);
counter++;
if (Interlocked.Decrement(ref tally) <= 0)
break;
Monitor.Exit(sync);
}
// broke while keeping the lock
Monitor.Exit(sync);
Console.WriteLine("{0}: {1}", name, counter);
}
Jun 27 '08 #10

P: n/a
I really do not know how to appreciate you but the original problem
still remains.
Let me clarify that I use VS .Net 2005 on Win XP SP2 and Intel CPU 2.4
MHZ.

I read your new code carefully.
This is what I deduced:

1- Two threads start at the same time.

2- Then one of them (depending on what OS does) enters the critical
section and gets the lock. The second one attempts to get the lock but
it will be blocks as other thread has not released the lock yet.

3-When the first thread (which has got the lock) releases the lock (by
calling Monitor.Exit) I expect that the second one (which was waiting
on "sysnc") now get the lock and increment the counter.

****
This is the problem! Can the second one accuires the lock before the
first one starts the next iteration?

If we write another code without "Interlocked.Decrement()" then this
new code will be exactly as the same as what I posted in the first
post. (I do not know really what Interlocked.Decrement does but I
insist on my question that why Entering and Existing are not enough)
So the code in my first post should run perfectly. But it does not.

Let me rephrase my question:
Is there enough time for OS to context switch and to give the control
to the waiting thread? (My experience with my own first code says no!)

So I can say that you solved the problem with something other than
Monitor.Exntr and Monitor.Exit(with "Interlocked.Decrement) . Am I
right?

Jun 27 '08 #11

P: n/a
On Jun 16, 5:09 pm, AliRezaGoogle <asemoo...@yahoo.comwrote:
I really do not know how to appreciate you but the original problem
still remains.
It would really help if you'd show us the problem in a complete
program then.

<snip>
This is the problem! Can the second one accuires the lock before the
first one starts the next iteration?

If we write another code without "Interlocked.Decrement()" then this
new code will be exactly as the same as what I posted in the first
post. (I do not know really what Interlocked.Decrement does but I
insist on my question that why Entering and Existing are not enough)
So the code in my first post should run perfectly. But it does not.
Your first post didn't include enough code to test anything.

Marc's post, by contrast, was a full program. The use of
Interlocked.Decrement was just to make sure that the loop (in each
thread) terminated. Each thread maintained a counter to show how many
times it had iterated. Interlocked.Decrement wasn't changing the
locking part at all.

If the second thread never had time to acquire the lock before the
first thread regained it, the numbers printed would be 100000 and 0.
Marc's numbers were fairly even, hence his contention that there isn't
a problem.

I doubt that it's *guaranteed* that the second thread will acquire the
lock - but I'd argue that any program whose correctness depended on
such a guarantee was far too brittle to start with.

Jon
Jun 27 '08 #12

P: n/a
On Jun 16, 7:59 pm, "Jon Skeet [C# MVP]" <sk...@pobox.comwrote:
On Jun 16, 5:09 pm, AliRezaGoogle <asemoo...@yahoo.comwrote:
I really do not know how to appreciate you but the original problem
still remains.

It would really help if you'd show us the problem in a complete
program then.

<snip>
This is the problem! Can the second one accuires the lock before the
first one starts the next iteration?
If we write another code without "Interlocked.Decrement()" then this
new code will be exactly as the same as what I posted in the first
post. (I do not know really what Interlocked.Decrement does but I
insist on my question that why Entering and Existing are not enough)
So the code in my first post should run perfectly. But it does not.

Your first post didn't include enough code to test anything.

Marc's post, by contrast, was a full program. The use of
Interlocked.Decrement was just to make sure that the loop (in each
thread) terminated. Each thread maintained a counter to show how many
times it had iterated. Interlocked.Decrement wasn't changing the
locking part at all.

If the second thread never had time to acquire the lock before the
first thread regained it, the numbers printed would be 100000 and 0.
Marc's numbers were fairly even, hence his contention that there isn't
a problem.

I doubt that it's *guaranteed* that the second thread will acquire the
lock - but I'd argue that any program whose correctness depended on
such a guarantee was far too brittle to start with.

Jon
Regarding to the code below my question is that why we have in out
put something like this:
Winer is T1
Winer is T1
Winer is T1
Winer is T2
Winer is T2
Winer is T2
Winer is T2
Winer is T2
Winer is T2
Winer is T1
Winer is T1
Winer is T1
Winer is T1
Winer is T1
......
......
......


class Program
{
static void Main()
{
Race r = new Race();
Thread t1 = new Thread(new ThreadStart(r.t1Proc));
Thread t2 = new Thread(new ThreadStart(r.t2Proc));

Thread.Sleep(500);

t1.Start();
t2.Start();
Console.ReadKey();
}
public class Race
{
Object SyncObj = new object();
int cntr = 0;
public void t1Proc()
{
while (true)
{
Monitor.Enter(SyncObj);
cntr++;
Console.WriteLine("Winer is {0} ", "T1", cntr);
Monitor.Exit(SyncObj);

if (cntr >= 10)
break;
//Thread.Sleep(700);
}

}
public void t2Proc()
{
while (true)
{
Monitor.Enter(SyncObj);
cntr++;
Console.WriteLine("Winer is {0} ", "T2", cntr);
Monitor.Exit(SyncObj);

if (cntr >= 10)
break;
//Thread.Sleep(700);
}
}
}
}
Jun 27 '08 #13

P: n/a
On Mon, 16 Jun 2008 23:40:56 -0700, AliRezaGoogle <as*******@yahoo.com>
wrote:
Regarding to the code below my question is that why we have in out
put something like this: [...]
You have that output because the first thread can accomplish more than one
iteration before Windows steps in and tells it that its current turn with
the CPU is up.

There's nothing fundamentally wrong with how the code you posted is
behaving. It's doing exactly what one would expect, given the relative
simplicity of the loop and the speed of a modern computer. Windows grants
CPU time to a particular thread in something like 50 millisecond blocks,
and it takes a lot less than 50 ms for the code you posted to get through
one iteration.

So, each thread gets to complete multiple iterations in a row, before the
other thread then gets its turn with the CPU.

Monitor.Wait() changes the behavior because it specifically waits (hence
the name). The "wait" causes the thread to yield the CPU, and thus the
other thread gets a chance to run. And in particular, the Monitor class
maintains a queue of threads waiting on the lock, and the thread that
called Wait() won't get the lock until every other thread that was already
in the queue before it gets a chance at the lock.

Other ways to change the behavior of your code would include simply
calling Thread.Sleep(1) (yields the CPU without specifically reentering
the queue for the lock), adding a spin-wait outside the section protected
by Monitor.Enter/Exit() (would cause the thread to use up its 50 ms
quantum without reaquiring the lock), and running the code on a computer
with more than one CPU (would allow the other thread to acquire the lock
as soon as the first thread released it, even if the first thread still
has CPU time left in its quantum).

Finally, some random thoughts on the code you posted:

-- There's no point in calling Thread.Sleep() from your main thread
after you create the Thread objects. You haven't started the threads, so
sleeping does absolutely nothing. Even if you had started the threads,
sleeping is pointless. The threads would still get to run eventually, and
not much later than you started them. And you could control your main
thread's coordination with the other threads in a much more precise,
useful way simply by calling Join() on each thread instance after you
start them.

-- There's no point in having two practically identical methods. Just
pass use the current thread's own ID, or pass some unique identifier to
the thread procedure, where there's just a single thread procedure used by
both threads.

-- If you do go back and modify the code for any reason, you might as
well fix it so that the word "winner" is spelled correctly. Maybe even
change the code so that the word is _used_ correctly too. :)

Pete
Jun 27 '08 #14

This discussion thread is closed

Replies have been disabled for this discussion.