简体   繁体   中英

Can scoped_lock lock a shared_mutex in read mode?

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_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

Mutex : Add thread safety to shared resources
Lock : Add RAII (and possibly extra functionality) to mutex

Different locks let you lock the mutex in different ways:

  • unique_lock: exclusive access to resource (for write)
  • shared_lock: shared access to resource (for simultaneous reads)
  • scoped_lock: same as unique_lock, but with less features

scoped_lock is a bare bone exclusive lock that is locked when constructed and unlocked when destroyed. unique_lock and shared_lock are exclusive and shared locks respectively that are also locked and unlocked with their default constructor and destructor. However, they also provide extra functionality. For example, you can try to luck them , or you can unlock them before they are destroyed.

So a typical use case would be to use a shared_lock for shared access (when multiple threads read the same resource), and use a unique_lock or a scoped_lock for exclusive access (depending on if you need the extra features of unique_lock or not).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM