Параллельное программирование на С++ в действии. Практика разработки многопоточных программ | страница 60



, потому что несколько ослабляет инварианты — экземпляр >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 lock_a(lhs.m, std::defer_lock);←┤

>   std::unique_lock lock_b(rhs.m, std::defer_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(), в противном случае — не должен. Опросить состояние флага позволяет функция-член