Why do nested locks not cause a deadlock?
If a thread already holds a lock, then it can "take that lock" again without issue.
As to why that is, (and why it's a good idea), consider the following situation, where we have a defined lock ordering elsewhere in the program of a -> b:
void f()
{
lock(a)
{ /* do stuff inside a */ }
}
void doStuff()
{
lock(b)
{
//do stuff inside b, that involves leaving b in an inconsistent state
f();
//do more stuff inside b so that its consistent again
}
}
Whoops, we just violated our lock ordering and have a potential deadlock on our hands.
We really need to be able to do the following:
function doStuff()
{
lock(a)
lock(b)
{
//do stuff inside b, that involves leaving b in an inconsistent state
f();
//do more stuff inside b so that its consistent again
}
}
So that our lock ordering is maintained, without self-deadlocking when we call f()
.
The lock
keyword uses a re-entrant lock, meaning the current thread already has the lock so it doesn't try to reacquire it.
A deadlock occurs if
Thread 1 acquires lock A
Thread 2 acquires lock B
Thread 1 tries to acquire lock B (waits for Thread 2 to be done with it)
Thread 2 tries to acquire lock A (waits for Thread 1 to be done with it)
Both threads are now waiting on each other and thus deadlocked.
From section 8.12 of the C# language specification:
While a mutual-exclusion lock is held, code executing in the same execution thread can also obtain and release the lock. However, code executing in other threads is blocked from obtaining the lock until the lock is released.
It should be obvious that the internal lock
scope is in the same thread as the outer.