Using Monitor.Enter to Lock Variable Changes

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 is the while loop inside the main function not interrupted even if I use a lock with the monitor? If I add Thread.Sleep (1) inside Thread, while everything works as expected, and even without Monitor ...

Is this happening too fast for the Monitor class to fail to lock?

Note: Operator proposed! =. I know that I can set it to <and solve the problem. What I was trying to achieve was to see how it works with the Monitor class and does not work without it. Unfortunately, this does not work in both directions. Thanks

+4
source share
4 answers

The Monitor key (or the lock keyword) is used to enter and exit the critical section. A critical section is a block of code that is guaranteed to be executed alternately with respect to any other critical section defined by the same object reference ( Monitor.Enter parameter). In other words, two or more threads executing critical sections defined by the same object reference should do this in such a way as to exclude their simultaneous execution. There is no guarantee that threads will do this in any particular order though.

For example, if we mark two critical section blocks of your code A and B with two threads as T1 and T2 , then any of the admissible below visualizes representations of the execution sequences.

 T1: AAA . . . A . AA . T2: . . . BBB . B . . B 

or

 T1: . AA . . AA T2: B . . BB . . 

or

 T1: AAAAAAA . T2: . . . . . . . B 

or

 T1: A . A . A . A . A . T2: . B . B . B . B . B 

The range of possible alternating permutations is infinite. I just showed you an infinitely small subset. It so happened that only the last permutation will lead to the fact that your program will work as you expected. Of course, that permutation is extremely unlikely, you introduce other mechanisms to make it happen.

You mentioned that Thread.Sleep(1) changed the behavior of your program. This is because it affects how the OS schedules thread execution. Thread.Sleep(1) is actually a special case that causes the calling thread to give its time slice to another thread of any processor. It was not clear to me where you put this call into your program, so I can not comment too much on why it gave the desired behavior. But I can say that this is mostly random.

In addition, I must indicate that you have a rather large error in this program. When you exit the while through break , you Monitor.Exit call to Monitor.Exit , which will leave the lock in the acquired state. It is much better to use the lock keyword because it will wrap Monitor.Enter and Monitor.Exit in a try-finally block, which ensures that the lock is always released.

+2
source

The first thread with while, can be scheduled twice in a row (i.e. the monitor may not be fair.)

See this related question: Is the lock () function guaranteed in the requested order?

+3
source

Suppose you have 1 processor. It will look like execution

  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 a thread
T2 is the main thread
[L][CK][UL] - block, check, unlock - main thread workload
CPU1 - task scheduling for the CPU

Note that short [T1] is a call to Thread.Sleep . This leads to the fact that the current thread gives control immediately. This thread will not be scheduled to run for a time greater than or equal to the specified milisecond parameter.

Longer [ T1 ] increments in the while .

Important: T1 will not perform any increments, and then switch to another thread. Here is the problem. It will perform many iterations until the current thread executes. On average, you can think of a quantum execution of ~ 10-30 milliseconds.

This is exactly supported by the output that was on my machine

  0
 0
 0
 ...
 56283
 56283
 56283
 ...
 699482
 699482
 699482
 ...
+3
source

Since a piece of the processor is usually 40 ms. During this time period, the thread copes with a large number of increments. This is not the case when the stream exits the monitor and immediately receives a context switch.

+2
source

Source: https://habr.com/ru/post/1501311/


All Articles