← Назад к вопросам
Какие знаешь виды mutex?
1.7 Middle🔥 241 комментариев
#Многопоточность и синхронизация
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI29 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Виды Mutex в C++
Mutex (mutual exclusion) — это примитив синхронизации для защиты общих ресурсов в многопоточной программе. В C++ STL есть несколько видов mutex с разными характеристиками.
1. std::mutex — базовый mutex
Обычный, неуникальный mutex:
#include <mutex>
#include <thread>
std::mutex mtx;
int shared_counter = 0;
void increment() {
for (int i = 0; i < 1000; ++i) {
mtx.lock(); // Захватить
shared_counter++; // Критическая секция
mtx.unlock(); // Освободить
}
}
int main() {
std::thread t1(increment);
std::thread t2(increment);
t1.join();
t2.join();
std::cout << shared_counter; // 2000 (безопасно)
}
Проблема: забыть unlock()
void bad_function() {
mtx.lock();
// ...
if (error) return; // DEADLOCK! mutex не освобождён
mtx.unlock();
}
Решение: std::lock_guard
void good_function() {
{
std::lock_guard<std::mutex> lock(mtx); // RAII!
// Критическая секция
// Автоматически unlock в конце scope
} // lock разрушается, mtx освобождается
}
// Даже при исключении:
void exception_safe() {
std::lock_guard<std::mutex> lock(mtx);
// ...
throw std::exception(); // lock всё равно освобождается
}
2. std::recursive_mutex — рекурсивный mutex
Позволяет одному потоку захватить mutex несколько раз:
std::recursive_mutex rmtx;
void recursive_function(int depth) {
std::lock_guard<std::recursive_mutex> lock(rmtx);
if (depth == 0) return;
std::cout << depth << std::endl;
recursive_function(depth - 1); // Тот же поток, нет deadlock!
}
int main() {
recursive_function(5); // Работает
}
// С обычным mutex бы был deadlock:
std::mutex regular_mtx;
regular_mtx.lock(); // 1-й раз
regular_mtx.lock(); // DEADLOCK! Этот же поток
Когда использовать
// ❌ Плохо — излишний recursive_mutex
class Counter {
private:
std::recursive_mutex mtx;
public:
int get() {
std::lock_guard<std::recursive_mutex> lock(mtx);
return value;
}
void increment() {
std::lock_guard<std::recursive_mutex> lock(mtx);
value++;
}
};
// ✅ Хорошо — обычный mutex
class Counter {
private:
std::mutex mtx;
public:
int get() {
std::lock_guard<std::mutex> lock(mtx);
return value;
}
void increment() {
std::lock_guard<std::mutex> lock(mtx);
value++;
}
};
3. std::timed_mutex — mutex с timeout
Можно указать максимальное время ожидания:
std::timed_mutex tmtx;
void try_with_timeout() {
// Попытка захватить на 1 секунду
if (tmtx.try_lock_for(std::chrono::seconds(1))) {
std::cout << "Захватили!" << std::endl;
// Критическая секция
tmtx.unlock();
} else {
std::cout << "Timeout! Mutex заблокирован другим потоком" << std::endl;
}
}
void try_until_deadline() {
auto deadline = std::chrono::system_clock::now() + std::chrono::seconds(5);
if (tmtx.try_lock_until(deadline)) {
std::cout << "Захватили!" << std::endl;
tmtx.unlock();
} else {
std::cout << "До deadline не смогли захватить" << std::endl;
}
}
Практическое использование
// Предотвращение deadlock в сложных сценариях
std::timed_mutex resource;
bool acquire_with_timeout() {
return resource.try_lock_for(std::chrono::milliseconds(100));
}
bool do_work() {
int attempts = 0;
while (attempts < 3) {
if (acquire_with_timeout()) {
// Работаем
resource.unlock();
return true;
}
attempts++;
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
return false; // Не удалось захватить
}
4. std::recursive_timed_mutex
Комбинация recursive и timed mutex:
std::recursive_timed_mutex rtmtx;
void recursive_with_timeout() {
if (rtmtx.try_lock_for(std::chrono::seconds(1))) {
std::cout << "1-й раз захватили" << std::endl;
// Тот же поток может захватить ещё раз
if (rtmtx.try_lock_for(std::chrono::seconds(1))) {
std::cout << "2-й раз захватили (рекурсия)" << std::endl;
rtmtx.unlock();
}
rtmtx.unlock();
}
}
5. std::shared_mutex — рекурсивный reader-writer mutex (C++17)
Множество читателей ИЛИ один писатель:
std::shared_mutex shared_mtx;
std::string shared_data = "initial";
void reader_thread() {
// Множество потоков могут одновременно читать
std::shared_lock<std::shared_mutex> lock(shared_mtx);
std::cout << "Reading: " << shared_data << std::endl;
} // Другие читатели всё ещё могут читать
void writer_thread() {
// Исключительный доступ (никто не читает)
std::unique_lock<std::shared_mutex> lock(shared_mtx);
shared_data = "updated";
} // Теперь читатели могут читать новое значение
int main() {
// Масса потоков-читателей
std::thread r1(reader_thread);
std::thread r2(reader_thread);
std::thread r3(reader_thread);
// Один поток-писатель (ждёт, пока читатели закончат)
std::thread w1(writer_thread);
r1.join(); r2.join(); r3.join();
w1.join();
}
Эффективность reader-writer mutex
Проблема с обычным mutex (множество читателей):
┌─────┐
│Read1│ захватывает mutex ─┐
└─────┘ ├─ Один за одним
┌─────┐ │
│Read2│ ждёт ───────────────┤
└─────┘ │
┌─────┐ │
│Read3│ ждёт ───────────────┘
└─────┘
Время: t1 + t2 + t3
С shared_mutex (множество читателей одновременно):
┌─────┐ Read1 ──┐
│Read1│ ├─ Одновременно!
└─────┘ Read2 ──┤
┌─────┐ │
│Read2│ Read3 ──┘
└─────┘
Время: max(t1, t2, t3)
6. std::unique_lock — продвинутый lock_guard
Гибче, чем lock_guard:
std::mutex mtx;
std::unique_lock<std::mutex> lock(mtx); // Захватывает
// Можем явно отпустить
lock.unlock();
// Другие потоки могут работать
lock.lock(); // Захватываем снова
// Можем проверить, захватили ли
if (lock.owns_lock()) {
std::cout << "У нас есть lock" << std::endl;
}
// Передача ownership
std::unique_lock<std::mutex> lock2 = std::move(lock);
// Теперь lock пусто, lock2 владеет
Сравнение всех видов
┌──────────────────────┬───────────┬────────────┬─────────┐
│ Тип │ Recursive │ Timeout │ RW │
├──────────────────────┼───────────┼────────────┼─────────┤
│ std::mutex │ Нет │ Нет │ Нет │
│ std::recursive_mutex │ Да │ Нет │ Нет │
│ std::timed_mutex │ Нет │ Да │ Нет │
│ std::recursive_timed │ Да │ Да │ Нет │
│ std::shared_mutex │ Да (shared)│ Нет │ Да │
└──────────────────────┴───────────┴────────────┴─────────┘
Best Practices
✅ ВСЕГДА используйте RAII:
std::lock_guard<std::mutex> lock(mtx); // Автоматический unlock
// Вместо:
mtx.lock();
// ...
mtx.unlock(); // Легко забыть!
✅ Минимизируйте критическую секцию:
// Плохо
std::lock_guard<std::mutex> lock(mtx);
expensive_computation(); // Держим lock во время дорогой операции
update_shared_data();
// Хорошо
expensive_computation(); // Без lock
{
std::lock_guard<std::mutex> lock(mtx);
update_shared_data(); // Только быстрая операция
}
✅ Используйте shared_mutex для read-heavy сценариев:
// Если много читателей, мало писателей — shared_mutex
std::shared_mutex cache_mtx;
// 1000 потоков читают одновременно
// 1 поток пишет редко
❌ Избегайте nested locks (deadlock):
std::mutex mtx1, mtx2;
void bad_function() {
mtx1.lock();
mtx2.lock(); // РИСК deadlock если другой поток
// захватит в другом порядке
}
✅ Используйте scoped_lock для нескольких mutex:
std::scoped_lock lock(mtx1, mtx2); // Избегает deadlock
// Автоматически отпускает в правильном порядке
Условные переменные (condition variables)
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
void waiter_thread() {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [] { return ready; }); // Спит до signal
std::cout << "Проснулись!" << std::endl;
}
void notifier_thread() {
std::this_thread::sleep_for(std::chrono::seconds(1));
{
std::lock_guard<std::mutex> lock(mtx);
ready = true;
}
cv.notify_one(); // Пробуди один waiter
// или cv.notify_all() для всех
}
Mutex — это основа для синхронизации в многопоточной программе. Правильное использование критично для избежания race conditions, deadlocks и других тонких ошибок.