What is a deadlock?

Viewed 149580

When writing multi-threaded applications, one of the most common problems experienced are deadlocks.

My questions to the community are:

  1. What is a deadlock?

  2. How do you detect them?

  3. Do you handle them?

  4. And finally, how do you prevent them from occurring?

18 Answers

A lock occurs when multiple processes try to access the same resource at the same time.

One process loses out and must wait for the other to finish.

A deadlock occurs when the waiting process is still holding on to another resource that the first needs before it can finish.

So, an example:

Resource A and resource B are used by process X and process Y

  • X starts to use A.
  • X and Y try to start using B
  • Y 'wins' and gets B first
  • now Y needs to use A
  • A is locked by X, which is waiting for Y

The best way to avoid deadlocks is to avoid having processes cross over in this way. Reduce the need to lock anything as much as you can.

In databases avoid making lots of changes to different tables in a single transaction, avoid triggers and switch to optimistic/dirty/nolock reads as much as possible.

Deadlocks will only occur when you have two or more locks that can be aquired at the same time and they are grabbed in different order.

Ways to avoid having deadlocks are:

  • avoid having locks (if possible),
  • avoid having more than one lock
  • always take the locks in the same order.

A deadlock happens when a thread is waiting for something that never occurs.

Typically, it happens when a thread is waiting on a mutex or semaphore that was never released by the previous owner.

It also frequently happens when you have a situation involving two threads and two locks like this:

Thread 1               Thread 2

Lock1->Lock();         Lock2->Lock();
WaitForLock2();        WaitForLock1();   <-- Oops!

You generally detect them because things that you expect to happen never do, or the application hangs entirely.

Deadlock occurs when two threads aquire locks which prevent either of them from progressing. The best way to avoid them is with careful development. Many embedded systems protect against them by using a watchdog timer (a timer which resets the system whenever if it hangs for a certain period of time).

Lock-based concurrency control

Using locking for controlling access to shared resources is prone to deadlocks, and the transaction scheduler alone cannot prevent their occurrences.

For instance, relational database systems use various locks to guarantee transaction ACID properties.

No matter what relational database system you are using, locks will always be acquired when modifying (e.g., UPDATE or DELETE) a certain table record. Without locking a row that was modified by a currently running transaction, Atomicity would be compromised).

What is a deadlock

A deadlock happens when two concurrent transactions cannot make progress because each one waits for the other to release a lock, as illustrated in the following diagram.

Deadlock

Because both transactions are in the lock acquisition phase, neither one releases a lock prior to acquiring the next one.

Recovering from a deadlock situation

If you're using a Concurrency Control algorithm that relies on locks, then there is always the risk of running into a deadlock situation. Deadlocks can occur in any concurrency environment, not just in a database system.

For instance, a multithreading program can deadlock if two or more threads are waiting on locks that were previously acquired so that no thread can make any progress. If this happens in a Java application, the JVM cannot just force a Thread to stop its execution and release its locks.

Even if the Thread class exposes a stop method, that method has been deprecated since Java 1.1 because it can cause objects to be left in an inconsistent state after a thread is stopped. Instead, Java defines an interrupt method, which acts as a hint as a thread that gets interrupted can simply ignore the interruption and continue its execution.

For this reason, a Java application cannot recover from a deadlock situation, and it is the responsibility of the application developer to order the lock acquisition requests in such a way that deadlocks can never occur.

However, a database system cannot enforce a given lock acquisition order since it's impossible to foresee what other locks a certain transaction will want to acquire further. Preserving the lock order becomes the responsibility of the data access layer, and the database can only assist in recovering from a deadlock situation.

The database engine runs a separate process that scans the current conflict graph for lock-wait cycles (which are caused by deadlocks). When a cycle is detected, the database engine picks one transaction and aborts it, causing its locks to be released, so that the other transaction can make progress.

Unlike the JVM, a database transaction is designed as an atomic unit of work. Hence, a rollback leaves the database in a consistent state.

A classic and very simple program for understanding Deadlock situation :-

public class Lazy {

    private static boolean initialized = false;

    static {
        Thread t = new Thread(new Runnable() {
            public void run() {
                initialized = true;
            }
        });

        t.start();

        try {
            t.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        System.out.println(initialized);
    }
}

When the main thread invokes Lazy.main, it checks whether the class Lazy has been initialized and begins to initialize the class. The main thread now sets initialized to false , creates and starts a background thread whose run method sets initialized to true , and waits for the background thread to complete.

This time, the class is currently being initialized by another thread. Under these circumstances, the current thread, which is the background thread, waits on the Class object until initialization is complete. Unfortunately, the thread that is doing the initialization, the main thread, is waiting for the background thread to complete. Because the two threads are now waiting for each other, the program is DEADLOCKED.

A deadlock is a state of a system in which no single process/thread is capable of executing an action. As mentioned by others, a deadlock is typically the result of a situation where each process/thread wishes to acquire a lock to a resource that is already locked by another (or even the same) process/thread.

There are various methods to find them and avoid them. One is thinking very hard and/or trying lots of things. However, dealing with parallelism is notoriously difficult and most (if not all) people will not be able to completely avoid problems.

Some more formal methods can be useful if you are serious about dealing with these kinds of issues. The most practical method that I'm aware of is to use the process theoretic approach. Here you model your system in some process language (e.g. CCS, CSP, ACP, mCRL2, LOTOS) and use the available tools to (model-)check for deadlocks (and perhaps some other properties as well). Examples of toolset to use are FDR, mCRL2, CADP and Uppaal. Some brave souls might even prove their systems deadlock free by using purely symbolic methods (theorem proving; look for Owicki-Gries).

However, these formal methods typically do require some effort (e.g. learning the basics of process theory). But I guess that's simply a consequence of the fact that these problems are hard.

Deadlock occurs when a thread is waiting for other thread to finish and vice versa.

How to avoid?
- Avoid Nested Locks
- Avoid Unnecessary Locks
- Use thread join()

How do you detect it?
run this command in cmd:

jcmd $PID Thread.print

reference : geeksforgeeks

Deadlocks does not just occur with locks, although that's the most frequent cause. In C++, you can create deadlock with two threads and no locks by just having each thread call join() on the std::thread object for the other.

Let's say one thread wants to transfer money from "A Account => B Account" and another thread wants to transfer money from "B Account => A Account", simultaneously. This can cause a deadlock.

The first thread will take a lock on "Account A" and then it has to take a lock on "Account B" but it cannot because second thread will already took lock on "Account B". Similarly second thread cannot take a lock on A Account because it is locked by the first thread. so this transaction will remain incomplete and our system will lose 2 threads. To prevent this, we can add a rule that locks the database records in sorted order. So thread should look at accounts name or IDs and decide to lock in a sorted order: A => B. There will be a race condition here and whoever wins, process its code and the second thread will take over. This is the solution for this specific case but deadlocks may occur in many reasons so each case will have a different solution.

Os has a deadlock detection mechanism with a certain interval of time, and when it detects the deadlock, it starts a recovery approach. More on deadlock detection

In the example we lost 2 threads but if we get more deadlocks, those deadlocks can bring down the system.

Mutex in essence is a lock, providing protected access to shared resources. Under Linux, the thread mutex data type is pthread_mutex_t. Before use, initialize it.

To access to shared resources, you have to lock on the mutex. If the mutex already on the lock, the call will block the thread until the mutex is unlocked. Upon completion of the visit to shared resources, you have to unlock them.

Overall, there are a few unwritten basic principles:

  • Obtain the lock before using the shared resources.

  • Holding the lock as short time as possible.

  • Release the lock if the thread returns an error.

Related