Table of Contents

C - C++ Threads - Troubleshooting - Deadlocks

The most common cause of a DEADLOCK is not acquiring multiple locks in the same order.

A situation where threads block indefinitely because they are waiting to acquire access to resources currently locked by other blocked threads.

Let's see an example:

Thread 1Thread 2
Lock ALock B
.. Do some processing..do some processing
Lock BLock A
.. Do some more processing..Do some more processing
Unlock BUnlock A
Unlock AUnlock B

NOTE: In some situations, what is going to happen is that when Thread 1 tries to acquire Lock B, it gets blocked because Thread 2 is already holding lock B.

And from Thread 2's perspective, it is blocked on acquiring lock A, but cannot do so because Thread 1 is holding lock A.

Thread 1 cannot release lock A unless it has acquired lock B and so on.

In other words, your program is hanged at this point.


Example to simulate a deadlock

#include <iostream>
#include <string>
#include <thread>
#include <mutex>
using namespace std;
 
std::mutex muA;
std::mutex muB;
 
 
void CallHome_AB(string message)
{
  muA.lock();
  //Some additional processing 
  std::this_thread::sleep_for(std::chrono::milliseconds(100));
  muB.lock();
  cout << "Thread " << this_thread::get_id() << " says " << message << endl;
  muB.unlock();
  muA.unlock();
}
 
 
void CallHome_BA(string message)
{
  muB.lock();
  //Some additional processing 
  std::this_thread::sleep_for(std::chrono::milliseconds(100));
  muA.lock();
  cout << "Thread " << this_thread::get_id() << " says " << message << endl;
  muA.unlock();
  muB.unlock();
}
 
 
int main()
{
  thread t1(CallHome_AB, "Hello from Jupiter");
  thread t2(CallHome_BA, "Hello from Pluto");
  t1.join();
  t2.join();
  return 0;
}

NOTE: If you run this, it will hang.

  • Go ahead and break into debugger to look at the threads window and you will see that Thread 1 (calling function CallHome_Th1()) is trying to acquire mutex B while thread 2 (calling function CallHome_Th2()) is trying to acquire mutex A.
  • None of them is making any progress because of the deadlock!

So, what can you do about it?

  • The best thing to do is to structure your code in such a way that all locks are acquired in the same order.
  • Depending on your situation,you can also employ the following strategies:
    1. Acquire locks together if both need to be acquired :
      std::scoped_lock lock{muA, muB};
    2. You can use a timed mutex where you can mandate that a lock be released after a timeout if it is not already available.

References

https://en.cppreference.com/w/cpp/thread/timed_mutex