Какой лучший способ заблокировать несколько std::mutex'ов?

Примечание. Этот вопрос касается C++11. Ответ на тот же вопрос в C++17 (или более поздних версиях), возможно, изменился. Для деталей:


Когда мы хотим заблокировать несколько std::mutexмы используем std::lock(), Но std::lock() не обеспечивает функцию RAII.

Когда мы хотим заблокировать std::mutex в RAII способ, мы используем std::lock_guard, Но std::lock_guard не может заблокировать несколько std::mutexбезопасно.

Есть ли способ воспользоваться преимуществами обоих методов, чтобы заблокировать несколько std::mutexв RAII пути?

2 ответа

Решение

Да, вы можете использовать std::unique_lock с std::defer_lock, Он указывает unique_lock не блокировать мьютекс сразу, а создать оболочку RAII.

std::unique_lock<std::mutex> lk1(mutex1, std::defer_lock);
std::unique_lock<std::mutex> lk2(mutex2, std::defer_lock);
std::lock(lk1, lk2);

Из-за своей вариативной природы std::lock не связан только с двумя аргументами, но может использоваться с таким количеством аргументов, которое поддерживается вашим компилятором.

Говард Хиннант также отметил интересный факт о производительности, вы можете проверить эту ссылку, если вы заинтересованы. Он решает проблемы производительности и показывает, что std::lock может быть реализовано эффективно, я также могу рекомендовать прочитать все комментарии в этом посте.

Как вы отметили, сам по себе не обеспечивает способ блокировки нескольких мьютексов без взаимоблокировок. Без безопасного метода вы столкнетесь с проблемой обедающих философов .

std::lockреализует алгоритм без взаимоблокировок, который блокирует несколько Lockable объектов. Его можно использовать с

      std::mutex m1, m2;

{   // Option A - lock mutexes first, adopt later
    std::lock(m1, m2);
    std::lock_guard<std::mutex> lock1(m1, std::adopt_lock);
    std::lock_guard<std::mutex> lock2(m2, std::adopt_lock);
    // critical section ...
}

{   // Option B - defer first, lock locks later
    std::unique_lock<std::mutex> lock1(m1, std::defer_lock);
    std::unique_lock<std::mutex> lock2(m2, std::defer_lock);
    std::lock(lock1, lock2);
    // critical section ...
}

{    // Option C - std::scoped_lock (C++17, but provided here for completeness)
    std::scoped_lock lock(m1, m2);
}

Если вам не нужны дополнительные функции, которыеstd::unique_lockобеспечивает (например, передачу права собственности на замок в другое место), затемstd::lock_guardследует отдать предпочтение.


Примечание: в примерах показаны только две блокировки, но все методы работают с сколь угодно большим количеством блокировок.

Другие вопросы по тегам