Что произойдёт, если повторно заблокировать (lock) std::mutex в том же потоке?

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

Ответ

Повторная попытка захватить нерекурсивный мьютекс (std::mutex) в одном и том же потоке приводит к неопределённому поведению (Undefined Behavior, UB). На практике это чаще всего вызывает взаимную блокировку (deadlock), так как поток бесконечно ждёт сам себя.

#include <mutex>

std::mutex mtx;

void risky_function() {
    mtx.lock();   // Первый захват – OK.
    // ... какая-то работа ...
    mtx.lock();   // ВТОРОЙ захват в том же потоке – НЕОПРЕДЕЛЁННОЕ ПОВЕДЕНИЕ (deadlock!).
    mtx.unlock();
    mtx.unlock();
}

Решение: Для сценариев, где один поток может нуждаться в многократном захвате одного и того же мьютекса, используйте рекурсивный мьютекс std::recursive_mutex.

#include <mutex>

std::recursive_mutex rec_mtx;

void safe_recursive_function(int depth) {
    rec_mtx.lock(); // Захват 1 (или N) – OK.
    // ... работа ...
    if (depth > 0) {
        safe_recursive_function(depth - 1); // Рекурсивный вызов снова захватит мьютекс.
    }
    rec_mtx.unlock(); // Каждому lock() должен соответствовать unlock().
}

Важно: Использование std::recursive_mutex часто указывает на проблему с дизайном кода. По возможности следует пересматривать структуру функций и область блокировки, чтобы избежать необходимости в рекурсивных блокировках.