← Назад к вопросам

Какие знаешь виды 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 и других тонких ошибок.

Какие знаешь виды mutex? | PrepBro