Шаблон std::unique_lock
обладает большей гибкостью, чем std::lock_guard
, потому что несколько ослабляет инварианты — экземпляр std::unique_lock
не обязан владеть ассоциированным с ним мьютексом. Прежде всего, в качестве второго аргумента конструктору можно передавать не только объект std::adopt_lock
, заставляющий объект управлять захватом мьютекса, но и объект std::defer_lock
, означающий, что в момент конструирования мьютекс не должен захватываться. Захватить его можно будет позже, вызвав функцию-член lock()
объекта std::unique_lock
(а std::lock()
сам объект std::unique_lock
. Код в листинге 3.6 можно было бы с тем же успехом написать, как показало в листинге 3.9, с применением std::unique_lock
и std::defer_lock()
(1) вместо std::lock_guard
и std::adopt_lock
. В новом варианте столько же строк, и он эквивалентен исходному во всем, кроме одной детали, — std::unique_lock
потребляет больше памяти и выполняется чуть дольше, чем std::lock_guard
. Та гибкость, которую мы получаем, разрешая экземпляру std::unique_lock
Листинг 3.9. Применение std::lock()
и std::unique_guard
для реализации операции обмена
class some_big_object;
void swap(some_big_object& lhs,some_big_object& rhs);
class X {
private:
some_big_object some_detail;
std::mutex m;
public:
X(some_big_object const& sd): some_detail(sd) {}
friend void swap(X& lhs, X& rhs) {
if (&lhs == &rhs)
std::defer_lock оставляет
return;
мьютексы не захваченными (1)
std::unique_lock
std::unique_lock
std::lock(lock_a, lock_b); ←
(2) Мьютексы захватываются
swap(lhs.some_detail, rhs.some_detail);
}
};
В листинге 3.9 объекты std::unique_lock
можно передавать функции std::lock()
(2), потому что в классе std::unique_lock
имеются функции-члены lock()
, try_lock()
и unlock()
. Для выполнения реальной работы они вызывают одноименные функции контролируемого мьютекса, а сами только поднимают в экземпляре std::unique_lock
флаг, показывающий, что в данный момент этот экземпляр владеет мьютексом. Флаг необходим для того, чтобы деструктор знал, вызывать ли функцию unlock()
. Если экземпляр unlock()
, в противном случае — owns_lock()
.
Естественно, этот флаг необходимо где-то хранить. Поэтому размер объекта std::unique_lock
обычно больше, чем объекта std::lock_guard
, и работает std::unique_lock
чуть медленнее std::lock_guard
, потому что флаг нужно проверять и обновлять. Если класс std::lock_guard
отвечает вашим нуждам, то я рекомендую использовать его. Тем не менее, существуют ситуации, когда std::unique_lock
лучше отвечает поставленной задаче, так как без свойственной ему дополнительной гибкости не обойтись. Один из примеров — показанный выше отложенный захват; другой — необходимость передавать владение мьютексом из одного контекста в другой.
3.2.7. Передача владения мьютексом между контекстами
Поскольку экземпляры std::unique_lock
не владеют ассоциированными мьютексами, то можно передавать владение от одного объекта другому путем std::move()
. Ситуация зависит от того, является ли источник std::unique_lock
дает пример