Avoiding Deadlocks
Swipe to show menu
Deadlocks are a critical concern in concurrent programming, especially when multiple threads attempt to acquire multiple locks. A deadlock occurs when two or more threads are each waiting for the other to release a resource, causing all of them to become stuck indefinitely. This situation often arises from specific patterns, such as when two threads acquire locks in different orders or when locks are held while waiting for another lock. Understanding the root causes of deadlocks is essential for designing robust concurrent systems.
deadlock_prevention.cpp
1234567891011121314151617181920212223242526#include <iostream> #include <thread> #include <mutex> #include <chrono> // Two shared resources, each protected by a mutex std::mutex mutexA; std::mutex mutexB; void avoid_deadlock() { // Lock both mutexes in a fixed order using std::lock std::lock(mutexA, mutexB); std::lock_guard<std::mutex> lock1(mutexA, std::adopt_lock); std::lock_guard<std::mutex> lock2(mutexB, std::adopt_lock); std::cout << "Thread 3 acquired both locks safely\n"; } int main() { std::thread t1(avoid_deadlock); std::thread t2(avoid_deadlock); t1.join(); t2.join(); }
The first two threads intentionally create a deadlock. Each thread locks one mutex and then attempts to lock the other, causing both threads to wait for each other indefinitely. As a result, the program will freeze
Deadlock prevention involves applying techniques that ensure your program cannot enter a deadlock state. Common strategies include:
-
Lock ordering: always acquire multiple locks in a consistent, predetermined order;
-
Using
std::try_lock: attempt to acquire locks without blocking, and back off if unable to acquire all; -
Avoiding nested locks: minimize the use of holding one lock while acquiring another.
In this example, the deadlock is avoided by locking both mutexes at the same time using std::lock.
The std::lock() function locks multiple mutexes using a deadlock-avoidance algorithm, ensuring that no thread holds one mutex while waiting for another. After locking, std::lock_guard with std::adopt_lock takes ownership of the already locked mutexes and manages their release automatically.
deadlock_example.cpp
1234567891011121314151617181920212223242526272829303132333435#include <iostream> #include <thread> #include <mutex> #include <chrono> // Two shared resources, each protected by a mutex std::mutex mutexA; std::mutex mutexB; void cause_deadlock() { std::lock_guard<std::mutex> lock1(mutexA); // Simulate some work std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::lock_guard<std::mutex> lock2(mutexB); std::cout << "Thread 1 acquired both locks\n"; } void cause_deadlock_opposite() { std::lock_guard<std::mutex> lock1(mutexB); // Simulate some work std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::lock_guard<std::mutex> lock2(mutexA); std::cout << "Thread 2 acquired both locks\n"; } int main() { std::thread t1(cause_deadlock); std::thread t2(cause_deadlock_opposite); t1.join(); t2.join(); }
Thanks for your feedback!
Ask AI
Ask AI
Ask anything or try one of the suggested questions to begin our chat