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.