Eduard Rostomyan
Eduard Rostomyan

Reputation: 6546

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??

Upvotes: 14

Views: 3730

Answers (3)

Fran&#231;ois Andrieux
Fran&#231;ois Andrieux

Reputation: 29022

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.

Upvotes: 12

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275490

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

Upvotes: 9

lisyarus
lisyarus

Reputation: 15532

  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.

Upvotes: 3

Related Questions