Анализ корректности и эффективности использования мьютексов

Два потока берут мьютексы в противоположном порядке, поэтому возможен deadlock. Сравнение объекта с самим собой также пытается повторно захватить один mutex. Оба случая требуют исправления.

Короткий ответ

Что ответить на собеседовании

Два потока берут мьютексы в противоположном порядке, поэтому возможен deadlock. Сравнение объекта с самим собой также пытается повторно захватить один mutex. Оба случая требуют исправления.

Подробный разбор

Ответ с пояснениями

Условие

Проведите ревью представленного кода: укажите возможные проблемы, предложите улучшения и оцените правильность применения мьютексов. Объясните, как работает оператор operator< в многопоточной среде.

# include <iostream>
# include <thread>
# include <mutex>

struct Data {
    explicit Data(size_t value)
        : m_value(value) {}

    friend bool operator<(Data& lhs, Data& rhs);

private:
    size_t m_value = 0u;
    size_t m_compare_counter = 0u;
    mutable std::mutex m_mutex;
};

bool operator<(Data& lhs, Data& rhs) {
    std::lock_guard<std::mutex> lhs_lock(lhs.m_mutex);
    std::lock_guard<std::mutex> rhs_lock(rhs.m_mutex);

    ++lhs.m_compare_counter;
    ++rhs.m_compare_counter;

    return lhs.m_value < rhs.m_value;
}

Data data_1(10u);
Data data_2(20u);

void compare_1() {
    for (size_t i = 0u; i < 100u; ++i) {
        const bool result = data_1 < data_2;
    }
}

void compare_2() {
    for (size_t i = 0u; i < 100u; ++i) {
        const bool result = data_2 < data_1;
    }
}

int main(int argc, char** argv) {
    std::thread thread_1(compare_1);
    std::thread thread_2(compare_2);

    if (thread_1.joinable()) {
        thread_1.join();
    }

    if (thread_2.joinable()) {
        thread_2.join();
    }

    return EXIT_SUCCESS;
}

Ответ

Два потока берут мьютексы в противоположном порядке, поэтому возможен deadlock. Сравнение объекта с самим собой также пытается повторно захватить один mutex. Оба случая требуют исправления.

Для текущей сигнатуры и счётчика участия каждого операнда:

bool operator<(Data& lhs, Data& rhs) {
    if (&lhs == &rhs) {
        std::lock_guard<std::mutex> lock(lhs.m_mutex);
        lhs.m_compare_counter += 2;
        return false;
    }
    std::scoped_lock lock(lhs.m_mutex, rhs.m_mutex);
    ++lhs.m_compare_counter;
    ++rhs.m_compare_counter;
    return lhs.m_value < rhs.m_value;
}

scoped_lock с двумя разными mutex использует безопасное совместное захватывание. Для обычного сравнения предпочтительнее const-параметры и отсутствие побочных эффектов; статистику можно вынести отдельно. Доступы к изменяемому m_value вне сравнения должны соблюдать ту же синхронизацию.

ИИ-помощник для собеседований

Хочешь уверенно проходить собеседования?

Попробуй ИИ-помощник для собеседований: слышит вас и собеседника, анализирует экран, подсказывает ответы в реальном времени, работает без VPN и не попадает в захват экрана.

Подробнее