Сколько мьютексов необходимо для возникновения deadlock?

«Сколько мьютексов необходимо для возникновения deadlock?» — вопрос из категории Многопоточность, который задают на 25% собеседований C/C++ Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Для классического взаимоблокирования (deadlock) необходимо как минимум два мьютекса. Ключевое условие — потоки захватывают их в разном порядке.

Пример на C++:

#include <thread>
#include <mutex>

std::mutex mtx1;
std::mutex mtx2;

void thread_a_work() {
    std::lock_guard<std::mutex> lock1(mtx1); // Захват первого
    // Имитация работы, во время которой нужен второй ресурс
    std::this_thread::sleep_for(std::chrono::milliseconds(10));
    std::lock_guard<std::mutex> lock2(mtx2); // Ожидание второго (заблокирован thread_b)
    // Критическая секция
}

void thread_b_work() {
    std::lock_guard<std::mutex> lock2(mtx2); // Захват второго
    std::this_thread::sleep_for(std::chrono::milliseconds(10));
    std::lock_guard<std::mutex> lock1(mtx1); // Ожидание первого (заблокирован thread_a)
    // Критическая секция
}

int main() {
    std::thread t1(thread_a_work);
    std::thread t2(thread_b_work);
    t1.join();
    t2.join(); // Программа может зависнуть здесь из-за deadlock
    return 0;
}

Как я избегаю этого:

  • Всегда захватываю мьютексы в строго одинаковом порядке во всех потоках.
  • Использую std::lock() или std::scoped_lock (C++17) для атомарного захвата нескольких мьютексов, что исключает deadlock.
    // Безопасный захват
    std::scoped_lock lock_all(mtx1, mtx2);