Deadlock in C# Threading


Introduction

A deadlock is a situation where an application locks up because two or more activities are waiting for each other to finish. This occurs in multithreading software where a shared resource is locked by one thread and another thread is waiting to access it and something occurs so that the thread holding the locked item is waiting for the other thread to execute.

First, it's important to understand what a deadlock among threads is and the conditions that lead to one. Many OS course textbooks will cite the four conditions necessary for a deadlock to occur:

  • A limited number of a particular resource. In the case of a monitor in C# (what you use when you employ the lock keyword), this limited number is one, since a monitor is a mutual-exclusion lock (meaning only one thread can own a monitor at a time).
  • The ability to hold one resource and request another. In C#, this is akin to locking on one object and then locking on another before releasing the first lock, for example:

lock(a)
   {
     lock(b)
         {
            ....
          }
   }

  • No preemption capability. In C#, this means that one thread can't force another thread to release a lock.
  • A circular wait condition. This means that there is a cycle of threads, each of which is waiting for the next to release a resource before it can continue.

If any one of these conditions is not met, deadlock is not possible. We can avoid all four condition by the followings:

  • The first condition is inherent to what a monitor is, so if you're using monitors, this one is set in stone.
  • The second condition could be avoided by ensuring that you only ever lock one object at a time, but that's frequently not a feasible requirement in a large software project.
  • The third condition could possibly be avoided in the Microsoft® .NET Framework by aborting or interrupting the thread holding the resource your thread requires, but a) that would require knowing which thread owned the resource, and b) that's an inherently dangerous operation .

To further illustrate how a deadlock might occur, imagine the following sequence of events:

  • Thread 1 acquires lock A.
  • Thread 2 acquires lock B.
  • Thread 1 attempts to acquire lock B, but it is already held by Thread 2 and thus Thread 1 blocks until B is released.
  • Thread 2 attempts to acquire lock A, but it is held by Thread 1 and thus Thread 2 blocks until A is released.

At this point, both threads are blocked and will never wake up. The following C# code demonstrates this situation.

object lockA = new object();
object lockB = new object();
        Thread 1 void t1()
        {
            lock (lockA)
            {
                lock (lockB)
                {
                    /* ... */
                }
            }
        }
        Thread 2 void t2()
        {
            lock (lockB)
            {
                lock (lockA)
                   {
                    /* ... */
                   }
            }
        }

We have another code which demonstrate the deadlock condition as:

using System;
using System.Threading;
namespace deadlockincsharp
{
public class Akshay
    {
        static readonly object firstLock = new object();
        static readonly object secondLock = new object();
        static void ThreadJob()
        {
            Console.WriteLine("\t\t\t\tLocking firstLock");
            lock (firstLock)
            {
                Console.WriteLine("\t\t\t\tLocked firstLock");
                // Wait until we're fairly sure the first thread
                // has grabbed secondLock
                Thread.Sleep(1000);
                Console.WriteLine("\t\t\t\tLocking secondLock");
                lock (secondLock)
                {
                    Console.WriteLine("\t\t\t\tLocked secondLock");
                }
                Console.WriteLine("\t\t\t\tReleased secondLock");
            }
            Console.WriteLine("\t\t\t\tReleased firstLock");
        }
        static void Main()
        {
            new Thread(new ThreadStart(ThreadJob)).Start();
            // Wait until we're fairly sure the other thread
            // has grabbed firstLock
            Thread.Sleep(500);
            Console.WriteLine("Locking secondLock");
            lock (secondLock)
            {
                Console.WriteLine("Locked secondLock");
                Console.WriteLine("Locking firstLock");
                lock (firstLock)
                {
                    Console.WriteLine("Locked firstLock");
                }
                Console.WriteLine("Released firstLock");
            }
            Console.WriteLine("Released secondLock");
            Console.Read();
        }      
    }
}

Output

 deadlock.gif

(You'll need to hit Ctrl-C or something similar to kill the program.) As you can see, each thread grabs one lock and then tries to grab the other. The calls to Thread.Sleep have been engineered so that they will try to do so at inopportune times, and deadlock.

Up Next
    Ebook Download
    View all
    Learn
    View all