Fundamentals 20 min read

Understanding and Preventing Deadlocks in C++ Multithreaded Programming

This article explains what deadlocks are in C++ multithreaded programming, outlines their causes and four necessary conditions, presents common scenarios and code examples, and offers practical strategies such as consistent lock ordering, std::lock, std::scoped_lock, recursive mutexes, and lock hierarchies to avoid them.

Deepin Linux
Deepin Linux
Deepin Linux
Understanding and Preventing Deadlocks in C++ Multithreaded Programming

1. What Is a Deadlock

A deadlock occurs when multiple threads each hold resources the others need and wait indefinitely, causing the entire program to stall.

1.1 Conditions for Deadlock

Four necessary conditions must hold simultaneously:

Mutual exclusion – a resource can be owned by only one thread at a time.

Hold and wait – a thread holds at least one resource while requesting additional ones.

No preemption – resources cannot be forcibly taken from a thread.

Circular wait – a closed chain of threads each waiting for a resource held by the next.

1.2 Harm of Deadlocks

Deadlocks waste resources, freeze program execution, and can even cause system crashes.

2. Common C++ Deadlock Scenarios

2.1 Inconsistent Lock Acquisition Order

When threads acquire multiple mutexes in different orders, a deadlock can arise. Example:

#include <iostream>
#include <thread>
#include <mutex>

std::mutex mutex1;
std::mutex mutex2;

void threadA() {
    mutex1.lock();
    std::cout << "Thread A acquired mutex1" << std::endl;
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    mutex2.lock();
    std::cout << "Thread A acquired mutex2" << std::endl;
    mutex2.unlock();
    mutex1.unlock();
}

void threadB() {
    mutex2.lock();
    std::cout << "Thread B acquired mutex2" << std::endl;
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    mutex1.lock();
    std::cout << "Thread B acquired mutex1" << std::endl;
    mutex1.unlock();
    mutex2.unlock();
}

int main() {
    std::thread t1(threadA);
    std::thread t2(threadB);
    t1.join();
    t2.join();
    return 0;
}

2.2 Re‑locking the Same Mutex (Non‑recursive Mutex)

Calling lock on a non‑recursive mutex that the same thread already holds leads to deadlock.

#include <iostream>
#include <thread>
#include <mutex>

std::mutex myMutex;

void recursiveFunction(int count) {
    if (count <= 0) return;
    myMutex.lock();
    std::cout << "Recursive function, count: " << count << std::endl;
    recursiveFunction(count - 1);
    myMutex.unlock();
}

int main() {
    std::thread t(recursiveFunction, 5);
    t.join();
    return 0;
}

2.3 Priority Inversion

Low‑priority threads holding a lock needed by a high‑priority thread can cause the high‑priority thread to block, potentially leading to deadlock.

3. How to Avoid Deadlocks in C++

3.1 Consistent Lock Order

Ensure all threads acquire multiple mutexes in the same global order.

3.2 Use std::lock to Acquire Multiple Locks Atomically

#include <iostream>
#include <thread>
#include <mutex>

std::mutex mutex1;
std::mutex mutex2;

void threadFunction() {
    std::lock(mutex1, mutex2);
    std::lock_guard<std::mutex> lock1(mutex1, std::adopt_lock);
    std::lock_guard<std::mutex> lock2(mutex2, std::adopt_lock);
    std::cout << "Thread successfully acquired mutex1 and mutex2" << std::endl;
}

int main() {
    std::thread t(threadFunction);
    t.join();
    return 0;
}

3.3 Use std::scoped_lock (C++17)

#include <iostream>
#include <thread>
#include <mutex>

std::mutex mutex1;
std::mutex mutex2;

void threadFunction() {
    std::scoped_lock lock(mutex1, mutex2);
    std::cout << "Thread successfully acquired mutex1 and mutex2" << std::endl;
}

int main() {
    std::thread t(threadFunction);
    t.join();
    return 0;
}

3.4 Use Recursive Mutexes When Re‑entrancy Is Required

#include <iostream>
#include <thread>
#include <mutex>

std::recursive_mutex recursiveMutex;

void recursiveFunction(int count) {
    if (count <= 0) return;
    recursiveMutex.lock();
    std::cout << "Recursive function, count: " << count << std::endl;
    recursiveFunction(count - 1);
    recursiveMutex.unlock();
}

int main() {
    std::thread t(recursiveFunction, 5);
    t.join();
    return 0;
}

3.5 Lock Hierarchy (Lock Leveling)

Define lock levels and forbid a thread from acquiring a higher‑level lock while holding a lower‑level one, breaking circular wait.

4. C++ Deadlock Case Study

4.1 Code Example

#include <iostream>
#include <thread>
#include <mutex>

std::mutex mutex1;
std::mutex mutex2;

void threadFunction1() {
    std::lock_guard<std::mutex> lock1(mutex1);
    std::cout << "Thread 1 acquired mutex1." << std::endl;
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    std::lock_guard<std::mutex> lock2(mutex2);
    std::cout << "Thread 1 acquired mutex2." << std::endl;
}

void threadFunction2() {
    std::lock_guard<std::mutex> lock2(mutex2);
    std::cout << "Thread 2 acquired mutex2." << std::endl;
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    std::lock_guard<std::mutex> lock1(mutex1);
    std::cout << "Thread 2 acquired mutex1." << std::endl;
}

int main() {
    std::thread t1(threadFunction1);
    std::thread t2(threadFunction2);
    t1.join();
    t2.join();
    return 0;
}

4.2 Analysis of Deadlock Causes

Mutual exclusion – each mutex can be owned by only one thread.

Hold and wait – each thread holds one mutex while waiting for the other.

No preemption – a thread cannot be forced to release a mutex it owns.

Circular wait – Thread 1 waits for mutex2 while Thread 2 waits for mutex1.

4.3 Detection Techniques

Logging lock acquisition and release times to spot cycles.

Debuggers (e.g., Visual Studio) to inspect thread states at runtime.

Static analysis tools (e.g., PVS‑Studio) that flag inconsistent lock ordering.

4.4 Resolution Strategies

(1) Enforce a uniform lock acquisition order, e.g., always lock mutex1 before mutex2 .

(2) Replace manual locking with std::lock to acquire both mutexes atomically.

(3) Apply timeout‑based lock attempts to avoid indefinite waiting.

concurrencydeadlockC++multithreadingMutexLock
Deepin Linux
Written by

Deepin Linux

Research areas: Windows & Linux platforms, C/C++ backend development, embedded systems and Linux kernel, etc.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.