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

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.

Leave a Comment

Hata!: SQLSTATE[HY000] [1045] Access denied for user 'divattrend_liink'@'localhost' (using password: YES)