Какие виды мьютексов знаешь в C++?

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

Ответ

Стандартная библиотека C++ предоставляет несколько типов мьютексов, каждый для конкретных сценариев синхронизации:

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

    std::mutex mtx;
    std::vector<int> shared_vec;
    
    void safe_push(int val) {
        std::lock_guard<std::mutex> lock(mtx); // RAII-захват
        shared_vec.push_back(val);
    } // Автоматическая разблокировка при выходе из области видимости
  2. std::recursive_mutex — рекурсивный мьютекс. Позволяет одному и тому же потоку захватывать его несколько раз. Необходимо вызвать unlock() столько же раз, сколько был вызван lock().

    std::recursive_mutex rec_mtx;
    void recursive_func(int x) {
        std::lock_guard<std::recursive_mutex> lock(rec_mtx);
        if (x > 0) {
            recursive_func(x - 1); // Безопасный рекурсивный вызов
        }
    }
  3. std::timed_mutex — расширяет std::mutex, добавляя методы try_lock_for() и try_lock_until(), которые позволяют попытаться захватить мьютекс с таймаутом.

    std::timed_mutex tmtx;
    if (tmtx.try_lock_for(std::chrono::milliseconds(100))) {
        // Захватили за 100 мс
        tmtx.unlock();
    } else {
        // Не удалось захватить, выполняем альтернативный код
    }
  4. std::recursive_timed_mutex — комбинация возможностей рекурсивного и timed мьютекса.

  5. std::shared_mutex (C++17) — мьютекс для реализации паттерна "множество читателей, один писатель". Позволяет нескольким потокам одновременно захватывать его в режиме разделяемой блокировки (lock_shared()) для чтения, но для записи требуется эксклюзивная блокировка (lock()).

    std::shared_mutex sh_mtx;
    std::string shared_data;
    
    // Поток-читатель
    {
        std::shared_lock lock(sh_mtx); // Разделяемая блокировка
        std::cout << shared_data;
    }
    
    // Поток-писатель
    {
        std::unique_lock lock(sh_mtx); // Эксклюзивная блокировка
        shared_data = "new value";
    }

Рекомендация: Всегда предпочитайте RAII-обёртки (std::lock_guard, std::unique_lock, std::shared_lock) для управления блокировками, чтобы избежать утечки блокировок при исключениях.