Ответ
std::mutex (mutual exclusion, взаимное исключение) — это базовый примитив синхронизации из стандартной библиотеки C++ (<mutex>), предназначенный для защиты общих данных от одновременного доступа нескольких потоков, предотвращая состояния гонки (race conditions).
Базовый пример (НЕ рекомендуется для production):
#include <iostream>
#include <thread>
#include <mutex>
#include <vector>
std::mutex g_console_mutex; // Мьютекс для защиты std::cout
int g_shared_counter = 0;
std::mutex g_counter_mutex;
void unsafe_increment() {
// ПРОБЛЕМА: между проверкой и модификацией может вклиниться другой поток
if (g_shared_counter < 100) {
// Имитация работы
std::this_thread::sleep_for(std::chrono::milliseconds(10));
++g_shared_counter;
}
}
void safe_increment() {
// Правильный подход: блокируем мьютекс на ВСЁ время работы с разделяемой переменной
std::lock_guard<std::mutex> lock(g_counter_mutex);
if (g_shared_counter < 100) {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
++g_shared_counter;
// Защищённый вывод в консоль
std::lock_guard<std::mutex> console_lock(g_console_mutex);
std::cout << "Thread " << std::this_thread::get_id()
<< ": counter = " << g_shared_counter << std::endl;
}
}
int main() {
std::vector<std::thread> threads;
for (int i = 0; i < 10; ++i) {
threads.emplace_back(safe_increment); // Используем безопасную версию
}
for (auto& t : threads) t.join();
std::cout << "Final counter: " << g_shared_counter << std::endl; // Всегда <= 100
return 0;
}
Ключевые классы для работы с мьютексами:
std::lock_guard— простейшая RAII-обёртка. Захватывает мьютекс в конструкторе и освобождает в деструкторе. Нельзя разблокировать явно или повторно захватить.std::unique_lock— более гибкая обёртка. Позволяет:- Откладывать захват (
std::defer_lock). - Явно вызывать
lock()/unlock(). - Работать с условными переменными (
std::condition_variable). - Передавать владение мьютексом.
- Откладывать захват (
Пример с std::unique_lock и условной переменной:
std::mutex mtx;
std::condition_variable cv;
bool data_ready = false;
void producer() {
std::this_thread::sleep_for(std::chrono::seconds(1));
{
std::lock_guard<std::mutex> lock(mtx);
data_ready = true;
}
cv.notify_one(); // Сигнализируем потребителю
}
void consumer() {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, []{ return data_ready; }); // Освобождает мьютекс и ждёт notify
// После пробуждения мьютекс снова захвачен
std::cout << "Data is ready!" << std::endl;
}
Важные нюансы:
- Взаимная блокировка (Deadlock): Возникает, когда два или более потока ждут друг друга. Лечится с помощью
std::lock(), который захватывает несколько мьютексов атомарно. - Производительность: Мьютекс — «тяжёлый» примитив. Для простых атомарных операций над одним типом данных предпочтительнее использовать
std::atomic. - Иерархия мьютексов (C++17):
std::mutex→std::timed_mutex(с таймаутами) →std::recursive_mutex(повторно захватываемый тем же потоком) →std::shared_mutex(C++17, для режима «много читателей / один писатель»).
Видео-ответы
▶
▶
▶
▶
▶
▶
▶
▶
▶
▶
▶
▶
▶