====== 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 1^Thread 2^ |Lock A|Lock B| |.. Do some processing|..do some processing| |Lock B|Lock A| |.. Do some more processing|..Do some more processing| |Unlock B|Unlock A| |Unlock A|Unlock 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 #include #include #include 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: - Acquire locks together if both need to be acquired : std::scoped_lock lock{muA, muB}; - You can use a [[https://en.cppreference.com/w/cpp/thread/timed_mutex|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