scoped_lock 可以在读取模式下锁定 shared_mutex 吗?

Can scoped_lock lock a shared_mutex in read mode?

C++17 同时引入了 std::shared_mutexstd::scoped_lock。我现在的问题是,当它作为参数传递时,scoped_lock 将始终以独占(编写器)模式锁定共享互斥锁,而不是共享(reader)模式。在我的应用程序中,我需要使用来自对象 src 的数据更新对象 dst。我想锁定 src 共享和 dst 独占。不幸的是,如果同时调用切换 srcdst 的另一个更新方法,则可能会出现死锁。所以我想使用 std::scoped_lock.

的奇特死锁避免机制

我可以使用 scoped_lock 在独占模式下同时锁定 srcdst,但是这种不必要的严格锁定会在其他地方产生性能倒退。然而,似乎可以将 srcshared_mutex 包装成 std::shared_lock 并将其与 scoped_lock 一起使用:当 scoped_lock 在其锁定操作在 shared_lock 上调用 try_lock(),后者实际上会在 srcshared_mutex 上调用 try_shared_lock(),这就是我需要的。

所以我的代码看起来很简单:

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???
}

在另一个(避免死锁)锁守卫中使用这样的(共享)锁守卫安全吗?

正如阅读过 C++ 标准库的实现代码的各种评论员所指出的:是的,使用 std::shared_mutex 包裹在 std::shared_lock() 中作为 [ 的参数之一=14=] 是安全的。

基本上,std::shared_lock 将所有对 lock() 的调用转发到互斥锁上的 lock_shared()

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

另一种可能的解决方案

std::shared_lock lk1(src.mutex, std::defer_lock);
std::unique_lock lk2(dst.mutex, std::defer_lock);
std::lock(lk1, lk2);

std::lock 是一个函数,它接受任意数量的 Lockable 对象并锁定所有对象(或异常中止,在这种情况下它们将全部解锁)。

根据 cppreference

std::scoped_lockstd::lock 的包装器,具有在其析构函数中的每个 Lockable 对象上调用 unlock() 的附加功能。这里不需要添加的功能,因为 std::shared_lock lk1std::unique_lock lk2 也可以用作锁守卫,当它们超出范围时解锁它们的互斥量。

编辑:各种说明

Mutex: 为共享资源添加线程安全
Lock:将 RAII(以及可能的额外功能)添加到 mutex

不同的锁让您以不同的方式锁定互斥量:

  • unique_lock: 对资源的独占访问(写)
  • shared_lock: 共享资源访问(同时读取)
  • scoped_lock:与unique_lock相同,但功能较少

scoped_lock是一个裸骨独占锁,构造时加锁,销毁时解锁。 unique_lockshared_lock 分别是独占锁和共享锁,它们也使用它们的默认构造函数和析构函数进行锁定和解锁。但是,它们还提供额外的功能。例如,您可以try to luck them,或者您可以在它们被摧毁之前解锁它们。

所以一个典型的用例是使用 shared_lock 进行共享访问(当多个线程读取相同的资源时),并使用 unique_lockscoped_lock 进行独占访问访问权限(取决于您是否需要 unique_lock 的额外功能)。