← Назад к вопросам

Можно ли попасть в 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;
}

Почему это дедлок?

  1. Поток T1 захватывает мьютекс m через lock1
  2. Поток T1 пытается захватить тот же мьютекс m через lock2
  3. Мьютекс уже захвачен T1, поэтому T1 блокируется, ожидая освобождения
  4. Но T1 никогда не сможет освободить мьютекс, так как он в состоянии ожидания
  5. Бесконечное ожидание = дедлок

Реальный пример с функциями

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, которое часто скрывает архитектурные проблемы.

Можно ли попасть в Deadlock на одном мьютексе? | PrepBro