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

Какой тип мьютекса можно повторно блокировать в том же потоке?

2.0 Middle🔥 201 комментариев
#Многопоточность и синхронизация

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI29 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Рекурсивное (повторное) блокирование мьютекса

Вопрос о повторной блокировке одного и того же мьютекса в одном потоке тесно связан с типом мьютекса. Разные типы мьютексов ведут себя по-разному в этой ситуации.

1. std::mutex — обычный мьютекс (не рекурсивный)

std::mutex НЕ поддерживает повторную блокировку в одном потоке. Попытка повторной блокировки приводит к дедлоку:

#include <mutex>

std::mutex m;

void deadlock_example() {
    m.lock();           // поток блокирует мьютекс
    
    std::cout << "First lock" << std::endl;
    
    m.lock();           // DEADLOCK! Поток ждёт сам себя
    std::cout << "Second lock" << std::endl;  // никогда не выполнится
    
    m.unlock();
    m.unlock();
}

Это происходит потому, что std::mutex предполагает, что поток владеет мьютексом только один раз. Повторный вызов lock() ждёт, пока мьютекс будет разблокирован, но поток занят самой блокировкой.

2. std::recursive_mutex — рекурсивный мьютекс

std::recursive_mutex можно повторно блокировать из одного и того же потока. Мьютекс отслеживает глубину блокировки (сколько раз один поток заблокировал его) и требует столько же разблокировок:

#include <mutex>

std::recursive_mutex rm;

void recursive_function(int depth) {
    rm.lock();
    
    std::cout << "Lock at depth " << depth << std::endl;
    
    if (depth > 0) {
        recursive_function(depth - 1);  // рекурсивный вызов
    }
    
    rm.unlock();
    std::cout << "Unlock at depth " << depth << std::endl;
}

int main() {
    recursive_function(3);
    return 0;
}

Внутренне std::recursive_mutex ведёт счётчик блокировок:

std::recursive_mutex rm;

void example() {
    rm.lock();      // счётчик = 1
    rm.lock();      // счётчик = 2
    rm.lock();      // счётчик = 3
    
    std::cout << "Inside triple lock" << std::endl;
    
    rm.unlock();    // счётчик = 2
    rm.unlock();    // счётчик = 1
    rm.unlock();    // счётчик = 0, мьютекс свободен
}

3. Типы мьютексов и их характеристики

  • std::mutex — обычный мьютекс, не поддерживает повторную блокировку в одном потоке
  • std::recursive_mutex — поддерживает повторную блокировку со счётчиком вложенности
  • std::timed_mutex — обычный мьютекс с таймаутом, не поддерживает повторную блокировку
  • std::recursive_timed_mutex — рекурсивный мьютекс с поддержкой таймаута

4. Примеры использования std::recursive_mutex

Случай 1: Рекурсивные функции

class FileProcessor {
private:
    std::recursive_mutex fileMutex;
    
public:
    void processFile(const std::string& filename, int level = 0) {
        std::lock_guard<std::recursive_mutex> lock(fileMutex);
        
        std::cout << "Processing: " << filename << std::endl;
        
        // Получение подфайлов (которые тоже нужно обработать)
        std::vector<std::string> subfiles = getSubfiles(filename);
        
        for (const auto& subfile : subfiles) {
            // Рекурсивный вызов - требует повторной блокировки
            processFile(subfile, level + 1);
        }
    }
};

Случай 2: Классы с методом, вызывающим другой метод

class DataContainer {
private:
    std::recursive_mutex dataMutex;
    std::vector<int> data;
    
public:
    void addData(int value) {
        std::lock_guard<std::recursive_mutex> lock(dataMutex);
        data.push_back(value);
    }
    
    void processAndAdd(int value) {
        std::lock_guard<std::recursive_mutex> lock(dataMutex);
        
        int processed = processValue(value);
        addData(processed);
    }
    
private:
    int processValue(int v) {
        return v * 2;
    }
};

5. RAII обёртки для мьютексов

Вместо явного lock()/unlock() используй std::lock_guard или std::unique_lock:

std::recursive_mutex rm;

void safe_example() {
    std::lock_guard<std::recursive_mutex> lock(rm);
    std::cout << "Mьютекс заблокирован" << std::endl;
}

void timed_lock_example() {
    std::recursive_timed_mutex rtm;
    
    std::unique_lock<std::recursive_timed_mutex> lock(rtm, std::chrono::milliseconds(100));
    
    if (lock.owns_lock()) {
        std::cout << "Успешно заблокирован" << std::endl;
    } else {
        std::cout << "Таймаут истёк" << std::endl;
    }
}

6. Недостатки recursive_mutex

Производительность: recursive_mutex медленнее обычного мьютекса из-за дополнительной памяти (счётчик, id потока) и операций при каждой блокировке.

Скрытые ошибки:

std::recursive_mutex rm;
int counter = 0;

void increment() {
    rm.lock();
    counter++;
    rm.unlock();
}

void double_increment() {
    rm.lock();
    increment();  // recursive lock - запутанный код
    rm.unlock();
}

7. Рекомендации

Используй std::recursive_mutex когда:

  • Действительно нужна рекурсивная блокировка (редко)
  • Рефакторить сложно (легаси-код)

Лучше использовать std::mutex когда:

  • Возможно избежать рекурсии (чаще всего)
  • Критична производительность
  • Новый код
// Плохо - использование recursive_mutex
std::recursive_mutex rm;

void processData() {
    std::lock_guard<std::recursive_mutex> lock(rm);
    processSubData();
}

void processSubData() {
    // Неявно требует блокировки от processData
}

// Хорошо - явное разделение
std::mutex m;

void processData() {
    std::lock_guard<std::mutex> lock(m);
    processSubDataUnlocked();
}

void processSubDataUnlocked() {
    // Явно требует, чтобы processData уже заблокировал мьютекс
}

Итого

Ответ на вопрос: std::recursive_mutex — это единственный мьютекс в стандартной библиотеке C++, который можно повторно блокировать из одного потока.

  • std::mutex — дедлок при повторной блокировке
  • std::recursive_mutex — поддерживает повторную блокировку со счётчиком
  • std::timed_mutex — дедлок, но с таймаутом
  • std::recursive_timed_mutex — рекурсивная блокировка с таймаутом

Лучше начинать со std::mutex и переходить на recursive_mutex только если абсолютно необходимо.

Какой тип мьютекса можно повторно блокировать в том же потоке? | PrepBro