Why doesn't this code end in a dead end

I have a C # code:

public class Locking { private int Value1; private int Value2; private object lockValue = new Object(); public int GetInt1(int value1, int value2) { lock (lockValue) { Value1 = value1; Value2 = value2; return GetResult(); } } public int GetInt2(int value1, int value2) { lock (lockValue) { return GetInt1(value1, value2); } } private int GetResult() { return Value1 + Value2; } } 

So basically I expect a dead end if I execute GetInt2 , but the code just executes. Any good explanation.

+3
source share
3 answers

The general case is whether the synchronization object is re-enabled. In other words, you can get the same thread again if it already owns the lock. Another way to tell if an object is "affinity for a stream".

In .NET, the Monitor class (which implements the lock statement), Mutex, and ReaderWriterLock are repetitive. There are no semaphore and SemaphoreSlim classes, you can get your code into a dead end with a binary semaphore. The cheapest way to implement locking is with Interlocked.CompareExchange (), it will also not be re-enabled.

There is an additional cost associated with creating a re-entry of the synchronization object; it needs to keep track of which thread belongs to it, and how often the lock was received in the ownership flow. This requires storing Thread.ManagedId and a counter, two ints. This influenced the choice in C ++, for example, the C ++ 11 language specification, finally adding streams to the standard library. The std :: mutex class is not a repeat participant in this language, and offers to add a recursive version were rejected. They considered the overhead of making him a repeat participant too high. It’s a little difficult, perhaps a cost that is quite small compared to the amount of time spent debugging a random deadlock :) But this is a language in which there is not the slightest chance that obtaining a stream identifier can be guaranteed cheap as it is. NET

This is displayed in the ReaderWriterLockSlim class, which you can select. Note the RecursionPolicy property, which allows you to choose between NoRecursion and SupportsRecursion. NoRecursion mode is cheaper and makes it really thin.

+4
source

lock locks an executable thread if that thread no longer holds an object lock.

In this case, only one thread is executed; he takes the lock on lockValue in GetInt2 , then goes to GetInt1 , where he again encounters the lock statement on lockValue - which he already has, so he is allowed to continue.

+11
source

The lock statement in C # is syntactic sugar, interpreted by the compiler as a call to Monitor.Enter . This is documented (in the "Monitors" section) that

 lock (x) { DoSomething(); } 

equivalently

 System.Object obj = (System.Object)x; System.Threading.Monitor.Enter(obj); try { DoSomething(); } finally { System.Threading.Monitor.Exit(obj); } 

The documentation for Monitor.Enter states that

For the same thread, Enter is allowed to be referenced several times without blocking; however, an equal number of Exit calls must be called before other threads waiting for the object are unblocked.

From what has been said, it is obvious that this code will not create deadlocks if only one thread is involved.

+7
source

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


All Articles