Ответ
В C++ для этого используется рекурсивный мьютекс (std::recursive_mutex). В отличие от обычного std::mutex, который при повторной попытке захвата тем же потоком приводит к неопределённому поведению или deadlock, рекурсивный мьютекс позволяет потоку многократно захватывать одну и ту же блокировку.
Пример использования:
#include <iostream>
#include <mutex>
std::recursive_mutex rec_mutex;
int shared_data = 0;
void recursive_function(int depth) {
std::lock_guard<std::recursive_mutex> lock(rec_mutex); // Захват #1
if (depth > 0) {
// Внутри критической секции снова нужен доступ к защищённым данным
std::lock_guard<std::recursive_mutex> lock_again(rec_mutex); // Захват #2, разрешён
shared_data += depth;
recursive_function(depth - 1); // Рекурсивный вызов также захватит мьютекс
}
}
int main() {
recursive_function(3);
std::cout << "Shared data: " << shared_data << std::endl; // Вывод: Shared data: 6
return 0;
}
Важные детали:
- Каждому успешному вызову
lock()(явному или черезstd::lock_guard/std::unique_lock) должен соответствовать вызовunlock(). Рекурсивный мьютекс ведёт внутренний счётчик. - Использовать
std::recursive_mutexстоит только когда это действительно необходимо (например, при вызове общедоступных методов класса, которые сами блокируются, из другого метода этого же класса). Часто архитектуру кода можно пересмотреть, чтобы избежать рекурсивных блокировок. - Для автоматического управления временем жизни блокировки всегда предпочитайте RAII-обёртки (
std::lock_guard,std::unique_lock).
Ответ 18+ 🔞
Смотри, бывает такая ситуация — пишешь ты код, всё логично, а потом бац — и сам себя заблокировал. Ну, как будто в туалет зашёл, дверь на ключ закрыл, а ключ-то внутри остался. И стоишь такой, охуевший, в полном недоумении. Так вот, чтобы с тобой такого не случилось, в C++ придумали рекурсивный мьютекс (std::recursive_mutex).
Обычный std::mutex — он строгий, блядь. Захватил его один раз в потоке и всё, второй раз уже не даст. Попробуешь — получишь deadlock, undefined behavior, и вообще, ёперный театр. А рекурсивный — он поумнее, он разрешает одному и тому же потоку захватывать себя несколько раз. Как будто у тебя ключ-дубликат в кармане.
Смотри, как это выглядит на практике:
#include <iostream>
#include <mutex>
std::recursive_mutex rec_mutex;
int shared_data = 0;
void recursive_function(int depth) {
std::lock_guard<std::recursive_mutex> lock(rec_mutex); // Захват #1
if (depth > 0) {
// Внутри критической секции снова нужен доступ к защищённым данным
std::lock_guard<std::recursive_mutex> lock_again(rec_mutex); // Захват #2, разрешён
shared_data += depth;
recursive_function(depth - 1); // Рекурсивный вызов также захватит мьютекс
}
}
int main() {
recursive_function(3);
std::cout << "Shared data: " << shared_data << std::endl; // Вывод: Shared data: 6
return 0;
}
Теперь про важные детали, а то накосячишь:
- Запомни, как отче наш: сколько раз вызвал
lock()(сам или черезstd::lock_guard), столько же раз должен быть иunlock(). Этот мьютекс внутри считает, как сумасшедший. Не сбалансируешь — пиши пропало. - Использовать эту штуку нужно с умом, а не потому что лень архитектуру продумать. Часто, если тебе реально нужен рекурсивный мьютекс, это знак, что код пора пересмотреть. Это как костыль — помогает идти, но хромать от этого не перестаёшь.
- И ради всего святого, не выёбывайся — всегда используй RAII-обёртки (
std::lock_guard,std::unique_lock). Они сами всё закроют, даже если твоя функция внезапно кинет исключение. А иначе будешь потом искать, где же этот чёртовunlockне вызвался, и волосы на жопе выдирать.