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

Ответ

Для классического взаимоблокирования (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);

Ответ 18+ 🔞

А, ну это классика жанра, ёпта! Смотри, чтобы устроить эту самую взаимную блокировку, когда все потоки повисли и ждут друг друга как идиоты, нужно как минимум два мьютекса. И главная фишка — потоки должны хватать их в разном порядке, как два упрямых барана, которые не хотят уступить дорогу.

Вот тебе наглядный пример на 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), чтобы атомарно захватить несколько мьютексов сразу. Это как взять все нужные бутерброды со стола одним движением, чтобы никто не успел перехватить.
    // Безопасный захват, и никакой хитрой жопы
    std::scoped_lock lock_all(mtx1, mtx2);