While reading on c++ std::lock
, I ran into the following example from the cppreference:
void assign_lunch_partner(Employee &e1, Employee &e2)
{
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;
}
// use std::lock to acquire two locks without worrying about
// other calls to assign_lunch_partner deadlocking us
{
std::lock(e1.m, e2.m);
std::lock_guard<std::mutex> lk1(e1.m, std::adopt_lock);
std::lock_guard<std::mutex> lk2(e2.m, std::adopt_lock);
// Equivalent code (if unique_locks are needed, e.g. for condition variables)
// 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);
// Superior solution available in C++17
// std::scoped_lock lk(e1.m, e2.m);
{
std::lock_guard<std::mutex> lk(io_mutex);
std::cout << e1.id << " and " << e2.id << " got locks" << std::endl;
}
e1.lunch_partners.push_back(e2.id);
e2.lunch_partners.push_back(e1.id);
}
send_mail(e1, e2);
send_mail(e2, e1);
}
While I do understand the need for making io_mutex
as static
so that its status is shared among concurrent calls to assign_lunch_partner
function (Please correct me if I'm wrong), but I don't understand the following:
- Why the
lk
object (lock_guard
) was scoped? Is it because of the naturelock_guard
? - Then if
lk
is scoped, does not this mean that the lock will be released once gone out of scope? - Why there are twice declaration of scoped
lk
(lock_guard
)? At the beginning and just before updatinglunch_partners
vectors?
You're correct in your understanding of why io_mutex is declared as static; this ensures that all concurrent calls to the function assign_lunch_partner will synchronize on the same mutex.
Now, let's answer your other questions:
Yes, it is because of the nature of
std::lock_guard
.std::lock_guard
acquires the lock in its constructor and releases the lock in its destructor. By placing thestd::lock_guard
object inside a scope (enclosed by curly braces {}), the lock will be released when the scope is exited, as the destructor ofstd::lock_guard
will be called.Yes, exactly. Once the scope is exited, the destructor for
std::lock_guard
is called, and the lock is released. This is a common pattern to limit the duration of a lock to just the section of code that needs synchronization.Why there are twice declarations of scoped lk (lock_guard)? At the beginning and just before updating lunch_partners vectors?
These two separate scoped lock guards are synchronizing different parts of the code:
lunch_partners
vectors. Again, this ensures that the console output from multiple threads is not interleaved.Essentially, these two separate scopes ensure that the messages print in a sensible and orderly manner even when this function is being called from multiple threads simultaneously. If the
io_mutex
lock were held for the entire duration of the function, it could potentially create a bottleneck and unnecessarily serialize parts of the code that don't need to be synchronized.Hopefully, that clears up the usage of the scoped lock guards in this code!