Conceptual Problem with Monitor.Enter and Monitor.Exit

A

AliRezaGoogle

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.
 
M

Marc Gravell

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

}
 
M

Marc Gravell

(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?)
 
A

AliRezaGoogle

(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!
 
M

Marc Gravell

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
 
A

AliRezaGoogle

No; you said the exact opposite:


So it sounds like you are saying "T1 always wins".


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.
 
M

Marc Gravell

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
 
A

AliRezaGoogle

Well, I really believe that we are seeing opposite behaviour.


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?
 
M

Marc Gravell

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);
}
}
 
M

Marc Gravell

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);
}
 
A

AliRezaGoogle

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?
 
J

Jon Skeet [C# MVP]

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.

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
 
A

AliRezaGoogle

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




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);
}
}
}
}
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Similar Threads

Inter-thread message queue in C# 6
c# Thread problem 20
Monitor.Wait problem 6
synchronization demo 7
Wait for synchronized access 9
Thread synchronization problem 7
Memory barriers, etc. 1
Dispatcher Threads 1

Top