My question is simple. In C++11 we have std::mutex
and std::lock_guard
and std::unique_lock
.
The usual way to use these classes is to lock the std::mutex
through any of the locks. This prevents leaking mutexes due to exception throwing:
{
std::lock_guard<std::mutex> l(some_mutex);
//Cannot leak mutex.
}
Why std::mutex::lock
and std::mutex::unlock
are public? That calls for incorrect usage:
{
some_mutex.lock();
//Mutex leaked due to exception.
some_mutex.unlock();
}
Wouldn't be safer to make std::lock_guard
and std::unique_lock
a friend of std::mutex
and make std::mutex
lock operations private? This would prevent unsafe usage.
The only reason I can guess for this design is that if I have my own lock, it couldn't be used with std::mutex
because I wouldn't be able to make my own lock friend of std::mutex
. Is this the main reason behind making these member functions public?
The benefit to using std::unique_lock<> comes from two things: you can transfer ownership of the lock between instances, and. the std::unique_lock<> object does not have to own the lock on the mutex it is associated with.
Mutexes are used to protect shared resources. If the mutex is already locked by another thread, the thread waits for the mutex to become available. The thread that has locked a mutex becomes its current owner and remains the owner until the same thread has unlocked it.
One advantage of OS mutexes is that they guarantee fairness: All threads waiting for a lock form a queue, and, when the lock is released, the thread at the head of the queue acquires it. It's 100% deterministic.
Secondly, the std::mutex is implemented in way that it spin locks for a bit before actually doing system calls when it fails to immediately obtain the lock on a mutex (which no doubt will be extremely slow).
The rationale for this design decision is documented in N2406:
Unlike boost, the mutexes have public member functions for lock(), unlock(), etc. This is necessary to support one of the primary goals: User defined mutexes can be used with standard defined locks. If there were no interface for the user defined mutex to implement, there would be no way for a standard defined lock to communicate with the user defined mutex.
At the time this was written, boost::mutex could only be locked and unlocked with boost::scoped_lock.
So you can now write my::mutex
, and as long as you supply members lock()
and unlock()
, your my::mutex
is just as much a first class citizen as std::mutex
. Your clients can use std::unique_lock<my::mutex>
just as easily as they can use std::unique_lock<std::mutex>
, even though std::unique_lock
has no clue what my::mutex
is.
A motivating real-life example of my::mutex
is the proposed std::shared_mutex
now in the C++1y (we hope y == 4) draft standard. std::shared_mutex
has members lock()
and unlock()
for the exclusive-mode locking and unlocking. And it interacts with std::unique_lock
when the client uses std::unique_lock<std::shared_mutex>
just as expected.
And just in case you might believe shared_mutex
might be the only other motivating mutex that could make use of this generic interface, here's another real-world example: :-)
template <class L0, class L1>
void
lock(L0& l0, L1& l1)
{
while (true)
{
{
unique_lock<L0> u0(l0);
if (l1.try_lock())
{
u0.release();
break;
}
}
this_thread::yield();
{
unique_lock<L1> u1(l1);
if (l0.try_lock())
{
u1.release();
break;
}
}
this_thread::yield();
}
}
This is the basic code for how to lock (in an exception safe manner) two BasicLockables at once, without danger of deadlock. And despite many critiques to the contrary, this code is extraordinarily efficient.
Note the line in the code above:
unique_lock<L0> u0(l0);
This algorithm has no clue what type L0
is. But as long as it supports lock()
and unlock()
(ok and try_lock()
too), everything is cool. L0
might even be another instantiation of unique_lock
, perhaps unique_lock<my::mutex>
or even unique_lock<std::shared_mutex>
, or perhaps even std::shared_lock<std::shared_mutex>
.
It all just works. And all because there is no intimate relationship between std::unique_lock
and std::mutex
aside from the agreed upon public interface of lock()
and unlock()
.
As Jerry Coffin pointed out, we can only speculate without an actual committee member chiming in, but my guess would be that they designed the interfaces with flexibility and the ability to extend them in your own way in mind. Instead of making every potential class a friend of std::mutex
, exposing the interface to do the needed operations allows for someone (e.g. boost contributors) to come along and find a new and better way to do something with it. If the interface was hidden, that would not be possible.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With