← Назад к вопросам
Можно ли попасть в Deadlock на одном мьютексе?
2.0 Middle🔥 101 комментариев
#Многопоточность и синхронизация#Язык C++
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI29 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Можно ли попасть в Deadlock на одном мьютексе?
Да, абсолютно можно. Дедлок на одном мьютексе возникает, когда один и тот же поток пытается захватить мьютекс дважды, а мьютекс не является рекурсивным.
Классический пример дедлока на одном мьютексе
#include <mutex>
#include <thread>
std::mutex m;
void processData() {
std::lock_guard<std::mutex> lock1(m);
// Имеем блокировку на m
// Попытка захватить тот же мьютекс второй раз
std::lock_guard<std::mutex> lock2(m); // ❌ ДЕДЛОК!
// Поток будет ждать вечно
}
int main() {
std::thread t(processData);
t.join();
return 0;
}
Почему это дедлок?
- Поток T1 захватывает мьютекс
mчерезlock1 - Поток T1 пытается захватить тот же мьютекс
mчерезlock2 - Мьютекс уже захвачен T1, поэтому T1 блокируется, ожидая освобождения
- Но T1 никогда не сможет освободить мьютекс, так как он в состоянии ожидания
- Бесконечное ожидание = дедлок
Реальный пример с функциями
class Database {
private:
mutable std::mutex db_mutex;
std::vector<int> data;
public:
void getData() const {
std::lock_guard<std::mutex> lock(db_mutex);
// Защищённый доступ к data
std::cout << "Чтение...\n";
}
void updateData() {
std::lock_guard<std::mutex> lock(db_mutex);
getData(); // ❌ ДЕДЛОК! Пытаемся снова захватить db_mutex
}
};
int main() {
Database db;
db.updateData(); // Зависает здесь
return 0;
}
Решение 1: Рекурсивный мьютекс (recursive_mutex)
class Database {
private:
mutable std::recursive_mutex db_mutex; // Разрешает повторный захват
std::vector<int> data;
public:
void getData() const {
std::lock_guard<std::recursive_mutex> lock(db_mutex);
std::cout << "Чтение...\n";
}
void updateData() {
std::lock_guard<std::recursive_mutex> lock(db_mutex);
getData(); // ✅ OK! Один поток может захватить несколько раз
}
};
Решение 2: Рефакторинг (лучше)
class Database {
private:
mutable std::mutex db_mutex;
std::vector<int> data;
// Приватный метод без блокировки
void getDataUnsafe() const {
std::cout << "Чтение...\n";
}
public:
void getData() const {
std::lock_guard<std::mutex> lock(db_mutex);
getDataUnsafe(); // Вызываем несокращённый вариант
}
void updateData() {
std::lock_guard<std::mutex> lock(db_mutex);
getDataUnsafe(); // ✅ OK! Явно вызываем беззащитную версию
}
};
Решение 3: Проверка владения мьютексом
#include <mutex>
#include <thread>
class Database {
pprivate:
std::mutex db_mutex;
public:
bool tryAcquire() {
return db_mutex.try_lock();
}
void release() {
db_mutex.unlock();
}
};
void safeProcess(Database& db) {
if (!db.tryAcquire()) {
std::cout << "Мьютекс уже захвачен этим потоком\n";
return;
}
// Работаем
db.release();
}
Сравнение подходов
| Подход | Плюсы | Минусы |
|---|---|---|
| recursive_mutex | Просто использовать | Медленнее обычного мьютекса, скрывает архитектурные проблемы |
| Рефакторинг (Locked/Unlocked) | Явно показывает слои блокировки | Требует больше кода |
| try_lock | Гибкость | Может быть сложнее отладить |
Лучшая практика
// ✅ Разделение на защищённый и незащищённый методы
class Database {
private:
std::mutex db_mutex;
// Внутренний метод — требует блокировки
void getDataImpl() const {
// Работает с защитой
}
public:
void getData() const {
std::lock_guard<std::mutex> lock(db_mutex);
getDataImpl();
}
void updateData() {
std::lock_guard<std::mutex> lock(db_mutex);
getDataImpl(); // Безопасно вызываем
}
};
Резюме
Дедлок на одном мьютексе — это реальная проблема, возникающая при попытке поиска повторного захвата. Лучшее решение — рефакторинг с разделением на защищённые и незащищённые методы, а не использование recursive_mutex, которое часто скрывает архитектурные проблемы.