Does the second lock provide any value?
The answer is obviously no, your intuitions are correct, but let's rationalize why.
The way a lock
works is by calling Monitor.Enter/Exit
.
When you take a lock
the CLR marks the header of the object (or its link to the sync table) atomically with a thread Id and a counter.
If another thread tries to lock over the object with an existing Thread ID
- It will perform a small spin wait (thin lock) to efficiently await the release without a context switch
- Failing that, it will take a more aggressive approach to promote the lock to an event with a operating system and wait on that handle (fat lock).
Each time the same thread calls the same lock
on the same object, it (in essence) just increments the counter of the object in the header or (sync block) and continues unabated until it exits the lock (Monitor.Exit
) at which point it decrements the counter. So on and so forth until the counter is zero.
So... do two nested locks on the same object in the same body of single threaded code achieve anything? Well the answer is no, apart from incrementing the lock counter (at a small cost).
So you might ask the question, what is the use of all this counter'ing? Well, it's actually for more complicated reentrant code scenarios, or where you have branching that may redirect the code to a lock over the same object... In that case, the same thread will not block on the same lock, in turn negating a very real deadlock situation.
Note : There are some components of how internal locking work that is CLR implementation and operating system specific, however the ECMA specs guarantees a certain consistent implementation of how these synchronization primitives' need to work in regard to behavior, reentrance, structural/emitted code and jit reordering.
Additional resources
For all the gory details you can see my answer here
Why Do Locks Require Instances In C#?
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…