Можно ли попасть в deadlock на одном мьютексе?

Ответ

Нет, классический deadlock (взаимная блокировка) требует циклической зависимости как минимум между двумя потоками и двумя ресурсами. На одном мьютексе это невозможно.

Однако, можно вызвать самоблокировку (self-deadlock), если поток попытается повторно захватить нерекурсивный мьютекс, который он уже удерживает.

#include <mutex>

std::mutex mtx;

void problematic_function() {
    mtx.lock();
    // ... какой-то код ...
    mtx.lock(); // Поток заблокируется здесь, ожидая самого себя -> deadlock
    mtx.unlock();
    mtx.unlock();
}

Решение:

  1. Использовать std::recursive_mutex, который позволяет одному потоку захватывать его несколько раз.
  2. Перепроектировать код, чтобы избежать повторного захвата. Часто это признак плохой структуры.
  3. Использовать std::lock_guard или std::unique_lock с std::defer_lock и std::try_lock для безопасных попыток захвата.
// Решение с recursive_mutex
std::recursive_mutex rec_mtx;

void safe_function() {
    std::lock_guard<std::recursive_mutex> lock(rec_mtx);
    // ... можно вызывать другую функцию, которая тоже захватывает этот мьютекс
}

Ответ 18+ 🔞

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

Хотя подожди... самому себе ногу наступить — запросто! Вот смотри, если поток возьмёт обычный мьютекс, а потом, не отпустив, опять попробует его же и схватить — всё, пиши пропало. Он сам себя будет ждать до второго пришествия, ядрёна вошь. Это называется самоблокировка.

#include <mutex>

std::mutex mtx;

void problematic_function() {
    mtx.lock();
    // ... какой-то код ...
    mtx.lock(); // Вот тут-то он и обосрётся! Сидит и ждёт, пока какой-то добрый молодец мьютекс отпустит. А добрый молодец — это он же и есть. Замкнутый круг, пиздец.
    mtx.unlock();
    mtx.unlock();
}

Ну и что с этим безобразием делать? Вариантов несколько, чувак.

  1. Рекурсивный мьютекс (std::recursive_mutex). Это как разрешить самому себе заходить в свой же холодильник сколько угодно раз. Захватил — иди кушай. Захотел ещё раз — да похуй, ты же уже внутри. Главное, чтобы отпустил потом ровно столько же раз, сколько взял, а то другим не достанется.

  2. Переделать архитектуру. Чаще всего, если тебе вдруг захотелось второй раз взяться за свой же замок — это тревожный звоночек. Может, код пора пересмотреть, чтобы такой ерунды не возникало? Подозрение ёбать чувствую к таким конструкциям.

  3. Умные обёртки и осторожные попытки. Использовать std::unique_lock с флагами вроде std::defer_lock и метод try_lock(). Это как аккуратно постучать в дверь: "Эй, я тут, можно я?" — и если там уже ты сидишь, то не ломиться, а пойти чайку попить.

// Вариант с рекурсивным — просто и для ленивых
std::recursive_mutex rec_mtx;

void safe_function() {
    std::lock_guard<std::recursive_mutex> lock(rec_mtx);
    // Теперь можешь хоть тысячу раз функции вызывать, которые тоже этот мьютекс берут — волнение ёбать нулевое.
}

Короче, вывод простой: на одном мьютексе полный взаимный клин не выйдет, но вот сесть на собственный же хуй и зависнуть — элементарно. Будь осторожен, не делай как тот Гамлет, который сам себя отрицал до посинения.