Ответ
Стандартная библиотека C++ предоставляет несколько типов мьютексов, каждый для конкретных сценариев синхронизации:
-
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); } // Автоматическая разблокировка при выходе из области видимости -
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); // Безопасный рекурсивный вызов } } -
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 { // Не удалось захватить, выполняем альтернативный код } -
std::recursive_timed_mutex— комбинация возможностей рекурсивного и timed мьютекса. -
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) для управления блокировками, чтобы избежать утечки блокировок при исключениях.
Ответ 18+ 🔞
Э, слушай, давай про мьютексы разберём, а то народ иногда нихуя не понимает, в чём разница. Ну, типа, стандартная библиотека C++ накидала нам целую овердохуищу разных мьютексов, и каждый — для своей хитрой жопы, то есть сценария.
Вот первый, 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);
} // Автоматическая разблокировка при выходе из области видимости
Смотри, я тут сразу юзаю lock_guard — это чтобы не обосраться и не забыть разблокировать, если вылетит исключение. Умно, да?
Дальше идёт std::recursive_mutex. Вот это уже интереснее. Представь, у тебя функция рекурсивная, или там сложная логика, и нужно из одного потока несколько раз войти в критическую секцию. С обычным мьютексом — сам себя заблокируешь, будешь сидеть и ждать, пока сам же освободишь — волнение ебать, а результата ноль. А этот чувак позволяет одному потоку захватывать себя сколько угодно раз. Главное — отпустить столько же раз, сколько взял, а то опять заклинит.
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); // Безопасный рекурсивный вызов
}
}
Потом у нас std::timed_mutex. Это типа того же базового, но с таймером. Ну, знаешь, бывает, ждёшь ресурс, а он занят, и сидишь, бздишь, как дурак, не зная, сколько ждать. А тут можно сказать: "Э, дружок-пирожок, я тебя 100 миллисекунд подожду, а потом пошёл ты на хуй, буду другим делом заниматься". Методы try_lock_for() и try_lock_until() — твои лучшие друзья в таком случае.
std::timed_mutex tmtx;
if (tmtx.try_lock_for(std::chrono::milliseconds(100))) {
// Захватили за 100 мс
tmtx.unlock();
} else {
// Не удалось захватить, выполняем альтернативный код
}
Ну и логично, что есть std::recursive_timed_mutex. Это гибрид, ёб твою мать, два в одном: и рекурсивный, и с таймаутом. Для совсем изощрённых извращенцев.
А теперь, внимание, самый сочный — 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 — это святое. Потому что если ты вручную lock() вызвал, а потом посередине твоего кода вылетело исключение — всё, пиши пропало, мьютекс повис намертво, deadlock, приехали. А эти штуки разблокируют всё автоматически, когда выходят из области видимости. Доверия ебать ноль к ручному управлению, только RAII. Запомни это как "Отче наш".