Какой лучший способ заблокировать несколько 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::adopt_lock
std::defer_lock
.
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
следует отдать предпочтение.
Примечание: в примерах показаны только две блокировки, но все методы работают с сколь угодно большим количеством блокировок.