Какие знаешь виды мьютексов из стандартной библиотеки?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Виды мьютексов в стандартной библиотеке C++
Мьютексы — это примитивы синхронизации для контроля доступа к общим ресурсам в многопоточной программе. C++ предоставляет несколько типов с разными свойствами и производительностью.
1. std::mutex (базовый мьютекс)
Описание: Базовый, не рекурсивный мьютекс.
#include <mutex>
std::mutex mtx;
// Захват и освобождение
mtx.lock();
// критическая секция
mtx.unlock();
// Или безопаснее с RAII:
{
std::lock_guard<std::mutex> lock(mtx);
// мьютекс автоматически освобождается
}
Свойства:
- Не рекурсивный (один поток не может заблокировать дважды)
- Простой и быстрый
- Не полезен для заданной временной длительности
Когда использовать: Базовый случай защиты общего ресурса между потоками.
2. std::recursive_mutex (рекурсивный мьютекс)
Описание: Позволяет одному потоку захватить мьютекс несколько раз.
std::recursive_mutex rmtx;
void function_a() {
std::lock_guard<std::recursive_mutex> lock(rmtx);
// делаем что-то
}
void function_b() {
std::lock_guard<std::recursive_mutex> lock(rmtx);
function_a(); // Рекурсивный вызов работает!
}
Сложность: Требует подсчёта глубины блокировки:
Поток T1:
lock() -> count = 1
lock() -> count = 2
lock() -> count = 3
unlock() -> count = 2
unlock() -> count = 1
unlock() -> count = 0 (полностью разблокирован)
Когда использовать: Когда функция может вызывать другую функцию, которая требует того же мьютекса.
Важно: Избегай рекурсивных мьютексов в production — часто признак плохого дизайна. Лучше переструктурировать код.
3. std::timed_mutex (мьютекс с timeout)
Описание: Базовый мьютекс с поддержкой timeout'а при захвате.
#include <mutex>
std::timed_mutex tmtx;
// Попытка захватить с timeout
if (tmtx.try_lock_for(std::chrono::milliseconds(100))) {
// Захватили в течение 100мс
// ...
tmtx.unlock();
} else {
// Не удалось захватить за 100мс
std::cout << "Timeout!" << std::endl;
}
// Или с абсолютным временем
std::chrono::steady_clock::time_point deadline =
std::chrono::steady_clock::now() + std::chrono::seconds(1);
if (tmtx.try_lock_until(deadline)) {
// ...
tmtx.unlock();
}
Свойства:
- try_lock_for(duration) — ждёт относительное время
- try_lock_until(time_point) — ждёт до абсолютного времени
- Медленнее обычного mutex из-за работы с временем
4. std::recursive_timed_mutex
Описание: Комбинация рекурсивного и таймирующего мьютекса.
std::recursive_timed_mutex rtmtx;
if (rtmtx.try_lock_for(std::chrono::milliseconds(100))) {
// Рекурсивный вызов с timeout'ом
rtmtx.lock(); // Второй захват
// ...
rtmtx.unlock();
rtmtx.unlock();
} else {
std::cout << "Timeout!" << std::endl;
}
Свойства:
- Самый универсальный, но и самый медленный
- Используется редко — обычно признак плохого дизайна
5. std::shared_mutex (читатель-писатель мьютекс) — C++17
Описание: Позволяет несколько читателей ИЛИ одного писателя одновременно.
#include <shared_mutex>
std::shared_mutex data_mtx;
int shared_data = 0;
// Читатель
void reader() {
std::shared_lock<std::shared_mutex> lock(data_mtx);
std::cout << shared_data; // Многие читатели могут одновременно
}
// Писатель
void writer(int value) {
std::unique_lock<std::shared_mutex> lock(data_mtx);
shared_data = value; // Только один писатель, читатели ждут
}
Сценарий:
Время: R1 R2 R3 W1 R4 R5
|-----|-----|-----| |-----|-----|
Читатели 1,2,3 могут работать одновременно (R1, R2, R3)
Писатель W1 ждёт, пока они закончат
Когда W1 пишет, читатели ждут
После W1, читатели 4,5 могут работать
Производительность:
- Для read-heavy workload: намного быстрее, чем обычный mutex
- Для write-heavy workload: медленнее из-за overhead'а
6. std::shared_timed_mutex — C++14
Описание: Комбинация shared_mutex с поддержкой timeout.
std::shared_timed_mutex stmtx;
// Читатель с timeout
if (stmtx.try_lock_shared_for(std::chrono::milliseconds(100))) {
// Что-то читаем
stmtx.unlock_shared();
}
// Писатель с timeout
if (stmtx.try_lock_for(std::chrono::milliseconds(100))) {
// Что-то пишем
stmtx.unlock();
}
Сравнение всех видов
| Мьютекс | Рекурсивный | Timeout | Чит/Писатель | Скорость | Юз-кейс |
|---|---|---|---|---|---|
| mutex | Нет | Нет | Нет | Быстро | Общие ресурсы |
| recursive_mutex | Да | Нет | Нет | Средне | Рекурсивный доступ |
| timed_mutex | Нет | Да | Нет | Средне | Тайм-ауты |
| recursive_timed_mutex | Да | Да | Нет | Медленно | Редко |
| shared_mutex | Нет | Нет | Да | Быстро* | Read-heavy данные |
| shared_timed_mutex | Нет | Да | Да | Средне | Read-heavy + timeout |
*Быстрее для читателей, но медленнее для писателей
Best Practices
1. Используй std::lock_guard или std::unique_lock (RAII)
// ПРАВИЛЬНО - автоматическое освобождение
{
std::lock_guard<std::mutex> lock(mtx);
// критическая секция
} // lock автоматически вызывает unlock()
// НЕПРАВИЛЬНО - может забыть unlock()
mtx.lock();
// если есть исключение, unlock() не будет вызван
mtx.unlock();
2. Минимизируй время в критической секции
std::vector<int> data;
std::mutex mtx;
// НЕПРАВИЛЬНО - долгая операция под мьютексом
{
std::lock_guard<std::mutex> lock(mtx);
auto item = data[0];
heavy_computation(item); // Долго!
}
// ПРАВИЛЬНО - только защищаем доступ к данным
int item;
{
std::lock_guard<std::mutex> lock(mtx);
item = data[0]; // Быстро
}
heavy_computation(item); // Снаружи мьютекса
3. Для read-heavy workload используй shared_mutex
std::shared_mutex config_mtx;
std::string config_value;
// Множество потоков могут читать одновременно
std::shared_lock<std::shared_mutex> lock(config_mtx);
std::cout << config_value;
4. Избегай рекурсивных мьютексов
Они медленнее и часто признак плохого дизайна. Лучше переструктурировать код.
Рекомендация для production
Для большинства backend-приложений:
- std::mutex — базовый выбор (95% случаев)
- std::shared_mutex — если много читателей
- Избегай тайм-аутов и рекурсии — обычно признак проблем дизайна
- Всегда используй lock_guard/unique_lock (RAII)