← Назад к вопросам
Что случится, если повторно заблокировать std::mutex в том же потоке?
2.0 Middle🔥 101 комментариев
#Многопоточность и синхронизация#Язык C++
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI29 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Что случится при повторной блокировке std::mutex в том же потоке
Ответ: DEADLOCK
std::mutex не является recursive (переусловный). Если один поток попытается заблокировать мьютекс, который он уже держит, произойдет deadlock — поток заблокируется самого себя и будет ждать неопределённо.
Пример проблемы
#include <mutex>
#include <thread>
#include <iostream>
std::mutex mtx;
void problematic_function() {
mtx.lock(); // Первая блокировка — успешна
std::cout << "First lock acquired" << std::endl;
mtx.lock(); // Вторая блокировка — DEADLOCK!
std::cout << "This will never print" << std::endl;
mtx.unlock();
mtx.unlock();
}
int main() {
problematic_function();
return 0;
}
Результат: программа зависнет. Второй lock() никогда не вернёт управление.
Почему это происходит
Мьютекс — это примитив синхронизации с состояниями:
- Unlocked — свободен
- Locked — заблокирован потоком A
Когда поток A держит мьютекс:
- Попытка другого потока заблокировать → ждёт (correct)
- Попытка того же потока A заблокировать → ждёт того, что уже имеет (deadlock)
Решение 1: std::recursive_mutex
#include <mutex>
std::recursive_mutex mtx; // Может быть заблокирован много раз
void safe_recursive_function(int depth) {
mtx.lock();
std::cout << "Depth: " << depth << std::endl;
if (depth > 0) {
safe_recursive_function(depth - 1); // Рекурсивный вызов
}
mtx.unlock();
}
int main() {
safe_recursive_function(3);
return 0;
}
Как работает:
recursive_mutexведёт счётчик блокировок- Один поток может заблокировать его N раз
- Требует N
unlock()для полного освобождения
mtx.lock(); // counter = 1
mtx.lock(); // counter = 2
mtx.lock(); // counter = 3
mtx.unlock(); // counter = 2
mtx.unlock(); // counter = 1
mtx.unlock(); // counter = 0 (освобожден)
Решение 2: std::unique_lock (рекомендуется)
#include <mutex>
std::mutex mtx;
void safe_function() {
std::unique_lock<std::mutex> lock(mtx);
std::cout << "Lock acquired" << std::endl;
// lock будет автоматически освобожден при выходе из scope
// Даже если выбросится исключение
}
Преимущества:
- RAII — автоматическое освобождение
- Исключения не вызывают утечку блокировки
- Более современный подход
Решение 3: std::lock_guard (самое простое)
#include <mutex>
std::mutex mtx;
void safe_function() {
std::lock_guard<std::mutex> guard(mtx);
// guard автоматически разблокирует мьютекс при выходе из scope
}
Сравнение решений
| Подход | Рекурсия | Производительность | Рекомендуется |
|---|---|---|---|
| std::mutex | Нет (deadlock) | Очень быстро | Для non-recursive кода |
| std::recursive_mutex | Да | Медленнее | Редко, обычно признак bad design |
| std::unique_lock | Зависит | Нормально | Обычно лучший выбор |
| std::lock_guard | Зависит | Очень быстро | Если не нужна гибкость |
Чекер на практике
#include <mutex>
#include <memory>
#include <iostream>
class DataContainer {
private:
std::mutex mtx;
int data = 0;
public:
void increment() {
std::lock_guard<std::mutex> guard(mtx);
data++;
}
void print_and_increment() {
std::lock_guard<std::mutex> guard(mtx);
std::cout << "Data: " << data << std::endl;
// Обычно НЕ вызываем increment() внутри,
// так как это вызовет deadlock с std::mutex
// Лучше вынести логику в отдельную функцию
data++; // Вместо этого делаем прямую операцию
}
};
Почему std::recursive_mutex редко используется
- Признак плохого дизайна — если функция часто нужна рекурсивно, переработайте архитектуру
- Медленнее — требует overhead на ведение счётчика
- Легче ошибиться — можно забыть про счётчик
- Усложняет отладку — deadlock вероятнее
Правило золотой середины
// ❌ Плохо: используешь recursive_mutex везде
std::recursive_mutex mtx; // Антипаттерн
// ✅ Хорошо: переструктурируешь код
std::mutex mtx; // Обычный мьютекс
void internal_impl_locked() {
// Внутренняя реализация, предполагает что мьютекс уже заблокирован
}
void public_function() {
std::lock_guard<std::mutex> guard(mtx);
internal_impl_locked();
}
Итоговый ответ
Если повторно заблокировать std::mutex в том же потоке:
- Произойдет deadlock
- Поток зависнет на неопределённое время
- Программа может зависнуть полностью
Решение:
- Переструктурировать код — refactor чтобы не было рекурсии
- Использовать
std::recursive_mutex— если действительно необходимо - Использовать RAII (
std::lock_guard,std::unique_lock) — для безопасности
Правило: избегай рекурсивного захвата мьютекса. Если система требует этого — переработай архитектуру.