std :: lock_guard или std :: scoped_lock?

143

C ++ 17 ввел новый класс блокировки под названием std::scoped_lock.

Судя по документации, он похож на уже существующий std::lock_guardкласс.

Какая разница и когда я должен его использовать?

Стефан Доллберг
источник

Ответы:

125

Это scoped_lockстрого улучшенная версия, lock_guardкоторая блокирует произвольное количество мьютексов одновременно (используя тот же алгоритм предотвращения тупиков, что и std::lock). В новом коде вы должны только когда-либо использовать scoped_lock.

Единственная причина lock_guardвсе еще существует для совместимости. Его нельзя просто удалить, поскольку он используется в текущем коде. Более того, оказалось нежелательным менять свое определение (с унарного на вариационное), потому что это также наблюдаемое и, следовательно, разрушительное изменение (но по некоторым техническим причинам).

Керрек С.Б.
источник
8
Кроме того, благодаря выводу аргументов шаблона класса, вам даже не нужно перечислять блокируемые типы.
Николь Болас
3
@NicolBolas: Это правда, но это также относится к lock_guard. Но это, безусловно, делает классы охраны немного проще в использовании.
Kerrek SB
6
scoped_lock - только C ++ 17
Shital Shah
1
Поскольку это c ++ 17, совместимость - это особенно веская причина для его существования. Я также категорически не согласен с любым абсолютистским заявлением о том, что «вы должны использовать только когда-либо», когда чернила все еще высыхают из этого стандарта.
Пол Чайлдс
88

Единственное и важное отличие состоит в том, что 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которую можно увидеть здесь .

Для получения дополнительной информации вы можете прочитать стандартную статью

Стефан Доллберг
источник
9
Ответил на свой вопрос только через 10 мин. Вы действительно не знали?
Уолтер
23
@Walter я сделал stackoverflow.blog/2011/07/01/…
Стефан Доллберг
3
Когда я поднял это в комитете, ответ был «ничего». Может случиться так, что в случае вырожденного алгоритма это как раз то, что нужно. Или может случиться так, что достаточное количество людей случайно ничего не блокирует, когда намеревалось что-то заблокировать, это общая проблема. Я действительно не уверен.
Говард
3
@HowardHinnant: scoped_lock lk; // locks all mutexes in scope. LGTM.
Kerrek SB
2
@KerrekSB: scoped_lock lk;это новое сокращение для scoped_lock<> lk;. Там нет ни одного мьютексов. Значит ты прав. ;-)
Говард
26

Поздний ответ, и в основном в ответ на:

Вы можете считать 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.

Поэтому мой совет - использовать самый простой инструмент для работы:

  1. lock_guard если вам нужно заблокировать ровно 1 мьютекс для всей области видимости.

  2. scoped_lock если вам нужно заблокировать несколько мьютексов, которые не точно 1.

  3. unique_lockесли вам нужно разблокировать в рамках блока (который включает в себя использование с condition_variable).

Этот совет вовсе не означает , что scoped_lockдолжен быть переработан , чтобы не принимать 0 семафоров. Существуют допустимые случаи использования, в которых желательно scoped_lockпринимать пакеты параметров вариабельного шаблона, которые могут быть пустыми. И пустой корпус не должен ничего блокировать.

И именно поэтому lock_guardне считается устаревшим. scoped_lock и unique_lock может быть расширенным набором функций lock_guard, но этот факт является обоюдоострым мечом. Иногда так же важно, что тип не будет делать (конструкция по умолчанию в этом случае).

Говард Хиннант
источник
13

Вот пример и цитата из 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, с меньшим риском ошибок, что может быть только хорошо!

陳 力
источник