Moving forward with Concurrency In Action I have reached the following example.
Tha author states that if we everytime lock 2 mutexes in the same order, then we are guaranteed to avoid deadlocks.
Consider this example from the book:
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){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);
}
};
std::lock
and then apply 2 std::lock_guards
with std::adopt_lock
instead of just applying 2 std::lock_guards
one after another?? std::mutex
es in the std::scoped_lock
?? std::lock. Locks the given Lockable objects lock1 , lock2 , ... , lockn using a deadlock avoidance algorithm to avoid deadlock. The objects are locked by an unspecified series of calls to lock , try_lock , and unlock .
std::lock_guard The class lock_guard is a mutex wrapper that provides a convenient RAII-style mechanism for owning a mutex for the duration of a scoped block. When a lock_guard object is created, it attempts to take ownership of the mutex it is given.
This compliant solution uses Standard Template Library facilities to ensure that deadlock does not occur due to circular wait conditions. The std::lock() function takes a variable number of lockable objects and attempts to lock them such that deadlock does not occur [ISO/IEC 14882-2014].
And the empty case should not lock anything. And that's why lock_guard isn't deprecated. scoped_lock and unique_lock may be a superset of functionality of lock_guard , but that fact is a double-edged sword. Sometimes it is just as important what a type won't do (default construct in this case).
Why do we apply the std::lock and then apply 2 std::lock_guards with std::adopt_lock instead of just applying 2 std::lock_guards one after another??
If you used two std::lock_guard
without std::lock
the order of locking for swap(a, b);
would be the opposite of swap(b, a);
, where a
and b
are X
s. If one thread tried swap(a, b);
while another tried swap(b, a);
they could deadlock. The first thread would own the lock on a
's mutex and wait for b
's while the second thread would own the lock on b
's mutex and wait for a
's. Using std::lock
ensures that the locking order is always consistent.
Why cant we just put this 2 std::mutexes in the std::scoped_lock??
If you look at the date of publication for the article you linked, c++17 did not exist yet. Since std::scoped_lock
was introduced by c++17, it could not have been used in the article. This kind of locking problem is what std::scoped_lock
is design to solve and should be used in modern code.
std::lock
is not RAII. Mutex locks not in RAII is dangerous and scary. If an exception is thrown you could "leak" a lock.
std::lock_guard
does not support deadlock safe multiple mutex locking. But it is RAII, so it makes the rest of the code safer. If you lock a then b in one spot, and b then a in another, you get code that can deadlock (with one thread holding a and waiting for b, and another thread holding b and waiting for a).
std::lock
is guaranteed to avoid this through some unspecified manner (which could include a global order on locks).
std::scoped_lock
is c++17. In c++17 it is what you should use instead of the sample code you have shown. It was added because writing that code sucks. Name mangling and linking issues prevented simply adding variardic support to existing locking primitives like lock guard, which is why it has a different name.
The reason is that std::lock
locks the mutexes in some unspecified order, yet the order is the same in all threads, thus protecting us from deadlocks. So, it may be lock(lhs.m)
and then lock(rhs.m)
, or the other way round. This means we don't know which of std::lock_guard
s to create first: for lhs.m
or for rhs.m
.
It seems that the book was written with C++11 as the basis standard. std::scoped_lock
comes only in C++17.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With