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?
The Scoped Locking C++ idiom ensures that a lock is acquired when control enters a scope and the lock is released automatically when control leaves the scope. Also Known As. Synchronized Block, Object-Construction-is-Resource-Acquisition.
The class shared_lock is a general-purpose shared mutex ownership wrapper allowing deferred locking, timed locking and transfer of lock ownership. Locking a shared_lock locks the associated shared mutex in shared mode (to lock it in exclusive mode, std::unique_lock can be used)
The shared_mutex class is a synchronization primitive that can be used to protect shared data from being simultaneously accessed by multiple threads.
As pointed out by various commentators, who have read the implementation code of the C++ standard library: Yes, the use of a std::shared_mutex
wrapped inside a std::shared_lock()
as one of the arguments to std::scoped_lock()
is safe.
Basically, a std::shared_lock
forwards all calls to lock()
to lock_shared()
on the mutex.
std::shared_lock::lock -----------> mutex()->lock_shared(). // same for try_lock etc..
Another possible solution
std::shared_lock lk1(src.mutex, std::defer_lock);
std::unique_lock lk2(dst.mutex, std::defer_lock);
std::lock(lk1, lk2);
std::lock
is a function that accepts any number of Lockable
objects and locks all of them (or aborts with an exception, in which case they will all be unlocked).
std::scoped_lock
according to cppreference is a wrapper for std::lock
, with the added functionaliy of calling unlock()
on each Lockable object in its destructor. That added functionality is not required here, as std::shared_lock lk1
and std::unique_lock lk2
also work as lock guards, that unlock their mutexes, when they go out of scope.
Edit: various clarifications
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