Deadlock


Let's see what it is, so we can avoid it!

 

namespace Concurrent;

public class DeadlockDemo
{
private readonly object _lockA = new();
private readonly object _lockB = new();

public void CauseDeadlock()
{
// Thread 1: Grabs Lock A, then tries for Lock B
var thread1 = new Thread(() =>
{
lock (_lockA)
{
Console.WriteLine("Thread 1: Acquired lock A. Waiting for lock B...");

// Small delay to ensure Thread 2 has time to grab lock B
Thread.Sleep(500);

lock (_lockB)
{
Console.WriteLine("Thread 1: Acquired lock B!");
}
}
});

// Thread 2: Grabs Lock B, then tries for Lock A
var thread2 = new Thread(() =>
{
lock (_lockB)
{
Console.WriteLine("Thread 2: Acquired lock B. Waiting for lock A...");

// Small delay to ensure Thread 1 has time to grab lock A
Thread.Sleep(100);

lock (_lockA)
{
Console.WriteLine("Thread 2: Acquired lock A!");
}
}
});

thread1.Start();
thread2.Start();

thread1.Join();
thread2.Join();

Console.WriteLine("This line will never be reached.");
}
}


Why this happens:

  1. Thread 1 enters _lockA.
  2. Thread 2 enters _lockB.
  3. Thread 1 tries to enter _lockB, but it’s held by Thread 2, so it waits.
  4. Thread 2 tries to enter _lockA, but it’s held by Thread 1, so it waits.
  5. They are now stuck in a cycle where neither can move forward.

How to avoid this in real applications:

  • Consistency: Always acquire locks in the same order (e.g., always _lockA then _lockB).
  • Timeouts: Use Monitor.TryEnter(lockObject, timeout) instead of the lock keyword.
  • Granularity: Keep your locked sections as small as possible and avoid performing I/O or calling external methods while holding a lock if possible.

No files yet, migration hasn't completed yet!