Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why put std::lock before std::lock_guard

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);
    }
};
  1. 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??
  2. Why cant we just put this 2 std::mutexes in the std::scoped_lock??
like image 489
Eduard Rostomyan Avatar asked Apr 26 '18 14:04

Eduard Rostomyan


People also ask

What does std :: lock do?

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 .

What is the role of std :: lock_guard?

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.

How does std :: lock avoid deadlock?

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].

Is lock_guard deprecated?

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).


3 Answers

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 Xs. 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.

like image 149
François Andrieux Avatar answered Oct 20 '22 23:10

François Andrieux


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.

like image 38
Yakk - Adam Nevraumont Avatar answered Oct 20 '22 23:10

Yakk - Adam Nevraumont


  1. 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_guards to create first: for lhs.m or for rhs.m.

  2. It seems that the book was written with C++11 as the basis standard. std::scoped_lock comes only in C++17.

like image 35
lisyarus Avatar answered Oct 20 '22 23:10

lisyarus