Какой тип мьютекса можно повторно блокировать в том же потоке?

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

Ответ

В C++ для этого используется рекурсивный мьютекс (std::recursive_mutex). В отличие от обычного std::mutex, который при повторной попытке захвата тем же потоком приводит к неопределённому поведению или deadlock, рекурсивный мьютекс позволяет потоку многократно захватывать одну и ту же блокировку.

Пример использования:

#include <iostream>
#include <mutex>

std::recursive_mutex rec_mutex;
int shared_data = 0;

void recursive_function(int depth) {
    std::lock_guard<std::recursive_mutex> lock(rec_mutex); // Захват #1
    if (depth > 0) {
        // Внутри критической секции снова нужен доступ к защищённым данным
        std::lock_guard<std::recursive_mutex> lock_again(rec_mutex); // Захват #2, разрешён
        shared_data += depth;
        recursive_function(depth - 1); // Рекурсивный вызов также захватит мьютекс
    }
}

int main() {
    recursive_function(3);
    std::cout << "Shared data: " << shared_data << std::endl; // Вывод: Shared data: 6
    return 0;
}

Важные детали:

  • Каждому успешному вызову lock() (явному или через std::lock_guard/std::unique_lock) должен соответствовать вызов unlock(). Рекурсивный мьютекс ведёт внутренний счётчик.
  • Использовать std::recursive_mutex стоит только когда это действительно необходимо (например, при вызове общедоступных методов класса, которые сами блокируются, из другого метода этого же класса). Часто архитектуру кода можно пересмотреть, чтобы избежать рекурсивных блокировок.
  • Для автоматического управления временем жизни блокировки всегда предпочитайте RAII-обёртки (std::lock_guard, std::unique_lock).