Reputation: 690
in the following code example:
class Program
{
private static int counter = 0;
public static object lockRef = new object();
static void Main(string[] args)
{
var th = new Thread(new ThreadStart(() => {
Thread.Sleep(1000);
while (true)
{
Monitor.Enter(Program.lockRef);
++Program.counter;
Monitor.Exit(Program.lockRef);
}
}));
th.Start();
while (true)
{
Monitor.Enter(Program.lockRef);
if (Program.counter != 100)
{
Console.WriteLine(Program.counter);
}
else
{
break;
}
Monitor.Exit(Program.lockRef);
}
Console.Read();
}
}
Why does the while loop inside Main function does not break even if I use lock with Monitor? If I add Thread.Sleep(1) inside the Thread while everything works as expected and even without Monitor…
Is it just happening too fast that the Monitor class doesn't have enough time to lock?
NOTE: The != operator is intended. I know I can set it to < and solve the problem. What I was trying to achieve is to see it working with Monitor class and not working without it. Unfortunately it doesn't work both ways. Thanks
Upvotes: 4
Views: 570
Reputation: 35905
Let's assume you have 1 CPU available. This is how the execution will look like
T1 [SLEEP][INCREMENT][SLEEP][INCREMENT][SLEEP][INCREMENT][SLEEP] T2 --[L][CK][UL][L][CK][UL][L][CK][UL][L][CK][UL][L][CK][UL][L][CK][UL] CPU1 [ T2 ][T1][ T2 ][ T1 ][ T2 ][T1][ T2 ][ T1 ][ T2 ][T1]...
Where:
T1
is th thread
T2
is main thread
[L][CK][UL]
is lock, check, unlock - the workload of the main thread
CPU1
is task scheduling for the CPU
Note a short [T1]
is a call to Thread.Sleep
. This results in the current thread yielding control immediately. This thread will not be scheduled for executing for time greater or equal to the specified milisecond parameter.
Longer [ T1 ]
is where increment in while
loop happens.
Important: T1
will not execute a single increment and then switch to another thread. This is where the problem. It will do many iterations until the current thread execution quant expires. On average you can think of execution quant ~ 10-30 mili seconds.
This is exactly supported by the output, which on my machine was
0 0 0 ... 56283 56283 56283 ... 699482 699482 699482 ...
Upvotes: 3
Reputation: 48949
The Monitor
class (or lock
keyword) is used to enter and exit a critical section. A critical section is a block of code that is guaranteed to execute serially relative any other critical section defined by the same object reference (the parameter to Monitor.Enter
). In other words, two or more threads executing critical sections defined by the same object reference must do so in such a manner that precludes them from happening simultaneously. There is no guarantee that the threads will do this in any particular order though.
For example, if we label the two critical section blocks of your code A
and B
the two threads as T1
and T2
then any of the following are valid visualize representations of the execution sequences.
T1: A A A . . . A . A A .
T2: . . . B B B . B . . B
or
T1: . A A . . A A
T2: B . . B B . .
or
T1: A A A A A A A .
T2: . . . . . . . B
or
T1: A . A . A . A . A .
T2: . B . B . B . B . B
The domain of possible interleaving permutations is infinite. I just showed you an infinitesimally small subset. It just so happens that only the last permutation will result in your program working the way you expected. Of course, that permutation is extremely unlikely useless you introduce other mechanisms to force it to happen.
You mentioned that Thread.Sleep(1)
changed the behavior of your program. This is because it is influencing how the OS schedules the execution of threads. Thread.Sleep(1)
is actually a special case that forces the calling thread to yield its time slice to another thread any processor. It was not clear to me where you put this call in your program so I cannot comment too much on why it delivered the desired behavior. But, I can say that it is mostly accidental.
Also, I have to point out that you have a pretty major bug in this program. When you jump out of the while
loop via break
you are bypassing the Monitor.Exit
call which will leave the lock in an acquired state. It is much better to use the lock
keyword because it will wrap the Monitor.Enter
and Monitor.Exit
into a try-finally
block that will guarantee that the lock will always be released.
Upvotes: 2
Reputation: 5357
Because CPU chunk is typically 40ms. During this timeframe the thread manages to do lots of increments. It's not the case that a thread exits a monitor and gets a context switch immediately.
Upvotes: 2
Reputation: 4621
The first thread with the while, might get scheduled twice in a row (ie the Monitor might not be fair.)
See this related question: Does lock() guarantee acquired in order requested?
Upvotes: 3