std::lock_guard или std::scoped_lock?
C++17 представил новый класс блокировки под названием std::scoped_lock
,
Судя по документации это выглядит похоже на уже существующий std::lock_guard
учебный класс.
Какая разница и когда я должен его использовать?
2 ответа
Единственное и важное отличие состоит в том, что std::scoped_lock
имеет конструктор с переменными числами, принимающий более одного мьютекса. Это позволяет заблокировать несколько взаимных блокировок в тупике, избегая, как будто std::lock
использовался.
{
// safely locked as if using std::lock
std::scoped_lock<std::mutex, std::mutex> lock(mutex1, mutex2);
}
Ранее вам нужно было немного потанцевать, чтобы безопасно заблокировать несколько мьютексов, используя std::lock
как объяснил этот ответ.
Добавление блокировки области упрощает использование и позволяет избежать связанных с этим ошибок. Вы можете рассмотреть std::lock_guard
осуждается. Случай единственного аргумента std::scoped_lock
может быть реализован как специализация, и вам не нужно опасаться возможных проблем с производительностью.
GCC 7 уже имеет поддержку std::scoped_lock
который можно увидеть здесь.
Для получения дополнительной информации вы можете прочитать стандартную статью
Поздний ответ, и в основном в ответ на:
Вы можете рассмотреть
std::lock_guard
устарело.
В общем случае, когда нужно заблокировать ровно один мьютекс, std::lock_guard
имеет API, который немного безопаснее в использовании, чем scoped_lock
.
Например:
{
std::scoped_lock lock; // protect this block
...
}
Приведенный выше фрагмент, вероятно, является случайной ошибкой во время выполнения, потому что он компилируется, а затем абсолютно ничего не делает. Кодер, вероятно, имел в виду:
{
std::scoped_lock lock{mut}; // protect this block
...
}
Теперь он блокирует / разблокируетmut
.
Если lock_guard
вместо этого использовался в двух приведенных выше примерах, первый пример - это ошибка времени компиляции, а не ошибка времени выполнения, а второй пример имеет ту же функциональность, что и версия, которая использует scoped_lock
.
Так что мой совет - использовать для работы самый простой инструмент:
lock_guard
если вам нужно заблокировать ровно 1 мьютекс для всей области.scoped_lock
если вам нужно заблокировать количество мьютексов, отличное от 1.unique_lock
если вам нужно разблокировать в рамках блока (что включает использование сcondition_variable
).
Этот совет вовсе не означает, чтоscoped_lock
должен быть переработан, чтобы не принимать 0 мьютексов. Существуют допустимые варианты использования, в которых желательноscoped_lock
принимать пакеты параметров вариативного шаблона, которые могут быть пустыми. И пустой корпус ничего не должен блокировать.
И поэтому lock_guard
не устарело. scoped_lock
а также unique_lock
может быть расширенным набором функций lock_guard
, но этот факт - палка о двух концах. Иногда не менее важно, что тип не будет делать (в данном случае конструкция по умолчанию).
scoped_lock
это строго улучшенная версия lock_guard
который блокирует произвольное количество мьютексов одновременно (используя тот же алгоритм предотвращения тупиков, что и std::lock
). В новом коде вы должны только когда-либо использовать scoped_lock
,
Единственная причина lock_guard
до сих пор существует для совместимости. Его нельзя просто удалить, потому что он используется в текущем коде. Более того, оказалось нежелательным менять свое определение (с унарного на вариационное), потому что это также наблюдаемое и, следовательно, разрушительное изменение (но по некоторым техническим причинам).
Вот пример и цитата из C++ Concurrency in Action:
friend void swap(X& lhs, X& rhs)
{
if (&lhs == & rhs)
return;
std::lock(lhs.m, rhs.m);
std::lock_guard<std::mutex> lock_a(lhs.m, std::adopt_lock);
std::lock_guard<std::mutex> lock_b(rhs.m, std::adopt_lock);
swap(lhs.some_detail, rhs.some_detail);
}
против
friend void swap(X& lhs, X& rhs)
{
if (&lhs == &rhs)
return;
std::scoped_lock guard(lhs.m, rhs.m);
swap(lhs.some_detail, rhs.some_detail);
}
Существование
std::scoped_lock
означает, что в большинстве случаев вы бы использовалиstd::lock
до C++17 теперь можно написать с помощьюstd::scoped_lock
с меньшим потенциалом для ошибок, которые могут быть только хорошими!