Какой механизм в C++ может атомарно заблокировать два мьютекса?

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

Ответ

В C++ для атомарной блокировки нескольких мьютексов с защитой от взаимоблокировок (deadlock) используются два основных механизма:

  1. std::lock (C++11): Алгоритмически безопасно захватывает несколько мьютексов, используя deadlock-avoidance алгоритм (например, алгоритм обедающих философов). После захвата мьютексы передаются RAII-обёрткам с флагом std::adopt_lock.

    std::mutex mtx1, mtx2;
    {
        std::lock(mtx1, mtx2); // Атомарный захват
        std::lock_guard<std::mutex> lock1(mtx1, std::adopt_lock);
        std::lock_guard<std::mutex> lock2(mtx2, std::adopt_lock);
        // Критическая секция защищена обоими мьютексами
    } // Мьютексы освобождаются автоматически
  2. std::scoped_lock (C++17): Более идиоматичная замена std::lock с variadic templates. Автоматически применяет deadlock-avoidance и обеспечивает RAII.

    std::mutex mtx1, mtx2;
    {
        std::scoped_lock lock(mtx1, mtx2); // Всё в одной строке
        // Критическая секция
    } // Автоматический unlock

Почему это важно? Последовательный захват (mtx1.lock(); mtx2.lock();) в разных потоках в обратном порядке — классическая причина deadlock. std::lock и std::scoped_lock гарантируют, что захват произойдёт атомарно для всей группы, исключая эту проблему.