C++17 introduced both std::shared_mutex and std::scoped_lock. My problem is now, that it seems, that scoped_lock will lock a shared mutex always in exclusive (writer) mode, when it is passed as an argument, and not in shared (reader) mode. In my app, I need to update an object dst with data from an object src. I want to lock src shared and dst exclusive. Unfortunately, this has the potential for deadlock, if a call to another update method with src and dst switched occurs at the same time. So I would like to use the fancy deadlock avoidance mechanisms of std::scoped_lock.
I could use scoped_lock to lock both src and dst in exclusive mode, but that unnecessarily strict lock has performance backdraws elsewhere. However, it seems, that it is possible to wrap src's shared_mutex into a std::shared_lock and use that with the scoped_lock: When the scoped_lock during its locking action calls try_lock() on the shared_lock, the later will actually call try_shared_lock() on src's shared_mutex, and that's what I need.
So my code looks as simple as this:
struct data {
mutable std::shared_mutex mutex;
// actual data follows
};
void update(const data& src, data& dst)
{
std::shared_lock slock(src.mutex, std::defer_lock);
std::scoped_lock lockall(slock, dst.mutex);
// now can safely update dst with src???
}
Is it safe to use a (shared) lock guard like this inside another (deadlock avoidance) lock guard?
As pointed out by various commentators, who have read the implementation code of the C++ standard library: Yes, the use of a
std::shared_mutexwrapped inside astd::shared_lock()as one of the arguments tostd::scoped_lock()is safe.Basically, a
std::shared_lockforwards all calls tolock()tolock_shared()on the mutex.Another possible solution
std::lockis a function that accepts any number ofLockableobjects and locks all of them (or aborts with an exception, in which case they will all be unlocked).std::scoped_lockaccording to cppreference is a wrapper forstd::lock, with the added functionaliy of callingunlock()on each Lockable object in its destructor. That added functionality is not required here, asstd::shared_lock lk1andstd::unique_lock lk2also work as lock guards, that unlock their mutexes, when they go out of scope.Edit: various clarifications