Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's the difference between first locking and creating a lock_guard(adopt_lock) and creating a unique_lock(defer_lock) and locking?

Tags:

I have found the following 2 pieces of code:

  1. http://en.cppreference.com/w/cpp/thread/lock

    void assign_lunch_partner(Employee &e1, Employee &e2)                                                                                                   {        // use std::lock to acquire two locks without worrying about      // other calls to assign_lunch_partner deadlocking us     {            // m is the std::mutex field         std::unique_lock<std::mutex> lk1(e1.m, std::defer_lock);         std::unique_lock<std::mutex> lk2(e2.m, std::defer_lock);         std::lock(lk1, lk2);         // ...     }    } 
  2. http://www.amazon.com/C-Concurrency-Action-Practical-Multithreading/dp/1933988770

    void swap(X& lhs, X&rhs){                                                                                                                                 if(&lhs == &rhs)     return;   // m is the std::mutex field   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); } 

I wanted to ask what is the difference and consequence of using either of 2 versions? (first lock or first create the std::lock_guard or std::unique_lock?)

like image 706
Patryk Avatar asked Nov 23 '14 13:11

Patryk


People also ask

What is the difference between Lock_guard and unique_lock?

A lock_guard always holds a lock from its construction to its destruction. A unique_lock can be created without immediately locking, can unlock at any point in its existence, and can transfer ownership of the lock from one instance to another.

Does unique_lock automatically unlock?

@Prab, just to give the explanation: unique_lock() is automatically released when the destructor is called meaning it is exception safe and that it automatically unlocks when you leave scope.

What is the benefit of using std :: unique_lock?

std::unique_lock This class guarantees an unlocked status on destruction (even if not called explicitly). Therefore it is especially useful as an object with automatic duration, as it guarantees the mutex object is properly unlocked in case an exception is thrown.

What is a unique_lock?

The class unique_lock is a general-purpose mutex ownership wrapper allowing deferred locking, time-constrained attempts at locking, recursive locking, transfer of lock ownership, and use with condition variables.

What is the difference between lock_guard and unique_lock in C++?

One of the differences between std::lock_guard and std::unique_lock is that the programmer is able to unlock std::unique_lock, but she/he is not able to unlock std::lock_guard. Let’s explain it in more detail. then the constructor of guard1 locks the mutex.

What is a lock guard in C++?

A lock guard is an object that manages a mutex object by keeping it always locked. On construction, the mutex object is locked by the calling thread, and on destruction, the mutex is unlocked. It is the simplest lock, and is specially useful as an object with automatic duration that lasts until the end of its context.

What is the difference between object lock1 and object lock2?

The constructor of the object lock1 locks the resource and when the object scope is over, the object destructor unlocks the mutex. The constructor of the object lock2 locks the resource and the object destructor unlocks the mutex. The difference is that the std::unique_lock consists of various other functionalities.

What is a lock in a transaction?

The transaction places locks on the various resources it needs to protect the referential integrity and database consistency. These locks will block other transactions from acquiring locks on the same resources until the transaction with the lock clears the lock (by either completing or being rolled back).


2 Answers

1) First code sample

{        static std::mutex io_mutex;     std::lock_guard<std::mutex> lk(io_mutex);     std::cout << e1.id << " and " << e2.id << " are waiting for locks" << std::endl; }    

This is a standard lock guard, when the scope is exited, the lock lk is released

{        std::unique_lock<std::mutex> lk1(e1.m, std::defer_lock);     std::unique_lock<std::mutex> lk2(e2.m, std::defer_lock);     std::lock(lk1, lk2);     std::cout << e1.id << " and " << e2.id << " got locks" << std::endl;     // ... }  

Here we first create the locks without acquiring them (that's the point of std::defer_lock) and then, using std::lock on both locks simultaneously makes sure that they are acquired without the risk of a deadlock if another caller of the function interleaves (we could have a deadlock if you replaced it with two successive calls to std::lock :

{        std::unique_lock<std::mutex> lk1(e1.m, std::defer_lock);     std::unique_lock<std::mutex> lk2(e2.m, std::defer_lock);     std::lock(lk1);     std::lock(lk2); // Risk of deadlock !     std::cout << e1.id << " and " << e2.id << " got locks" << std::endl;     // ... }  

2) Second code sample

void swap(X& lhs, X&rhs){                                                                                                                                 if(&lhs == &rhs)     return;   // m is the std::mutex field   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); } 

Now, here we first acquire the locks (still avoiding deadlocks), and then we create the lockguards to make sure that they are properly released.

Note that std::adopt_lock requires that the current thread owns the mutex (which is the case since we just locked them)


Conclusion

There are 2 patterns here :

1) Lock both mutex at the same time, then create the guards

2) Create the guards, then lock both mutex at the same time

Both patterns are equivalent, and aim at the same thing : safely lock two mutex at the same time, and ensure that unlocking always occur for both.

As for the difference between std::lock_guard and std::unique_lock, you should see this other SO post, most of the time std::lock_guard is enough.

like image 70
quantdev Avatar answered Sep 20 '22 23:09

quantdev


There's actually a paragraph (3.2.6) in the book explaining that the code is virtually equivalent and you could replace one with the other. The only difference being is that std::unique_lock tends to take more space and is a fraction slower than std::lock_guard.

Bottom line is whenever you don't need the additional flexibility that std::unique_lock provides, go with std::lock_guard.

like image 29
Jiří Pospíšil Avatar answered Sep 19 '22 23:09

Jiří Pospíšil