Can scoped_lock lock a shared_mutex in read mode?


February 2019


176 time


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?

1 answers


So the problem is simple. A std::shared_lock defers all its lock calls to its shared counterpart.

std::shared_lock::lock -----------> mutex()->lock_shared(). // same for try_lock etc..

Because scoped_lock wants objects of mutex_type. And because std::shared_lock is not a derived class from mutex. mutex_type* shared_lock::mutex() const noexcept; gets called and its underlying Mutex gets exposed instead.

A 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 Lockable and tries to lock all of them. std::scoped_lock according to cppreference is merely a wrapper for std::lock