std::mutex 和 std::shared_mutex 之间的区别

Posted

技术标签:

【中文标题】std::mutex 和 std::shared_mutex 之间的区别【英文标题】:difference between std::mutex and std::shared_mutex 【发布时间】:2017-09-27 16:32:56 【问题描述】:

我在C++17 中遇到了std::shared_mutexstd::shared_mutex 到底是什么,它与 std::mutex 有何不同?

【问题讨论】:

一个简单的想法是:一个shared_mutex就像一个flock() 【参考方案1】:

如the documentation中所述

shared_mutex 类是一个同步原语,可用于保护共享数据不被多个线程同时访问。与其他便于独占访问的互斥锁类型相比,shared_mutex 具有两个访问级别:

共享 - 多个线程可以共享同一个互斥锁的所有权。 独占 - 只有一个线程可以拥有互斥锁。

共享互斥锁通常用于多个读取器可以同时访问同一资源而不会导致数据争用但只有一个写入器可以这样做的情况。

这有多种用途,但一个常见的用途是实现Read Write Lock,您可以让多个线程读取共享数据,但任何时候只有一个线程独占写入。因此,当您有多个读取器时,互斥锁以“共享模式”运行,但当请求写入时,它会变为“独占模式”。

【讨论】:

【参考方案2】:

mutex 要么被锁定,要么未被锁定。

shared_mutex 要么被独占锁定,要么被共享锁定。

任意数量的客户端都可以共享锁定一个共享互斥锁。

如果有人将其独占锁定,则其他人无法持有任何锁。

在windows上,这是SWRLOCK类型——事实上,这个锁通常用于实现读写锁;允许许多读者,但写作必须是独占的。

这里是一些示例代码,用于为共享和非共享互斥体创建两个模板包装器。在一种情况下,我们有获取不同锁的读取和写入操作。另一方面,我们只能访问:

template<class T, class M=std::mutex>
struct mutex_guarded 
  template<class F>
  auto access( F&& f ) 
    auto l = lock();
    return std::forward<F>(f)(t);
  
  template<class F>
  auto access( F&& f ) const 
    auto l = lock();
    return std::forward<F>(f)(t);
  
  mutex_guarded(mutex_guarded const&)=delete;
  mutex_guarded& operator=(mutex_guarded const&)=delete;
  template<class...Ts>
  mutex_guarded( Ts&&...ts ):t(std::forward<Ts>(ts)...)
  mutex_guarded()=default;
protected:
  mutable M m;
  T t;
  auto lock()  return std::unique_lock<M>(m); 
;
template<class T, class M=std::shared_mutex>
struct shared_mutex_guarded:private mutex_guarded<T, M> 
  using base = mutex_guarded<T, M>;
  template<class F>
  auto read( F&& f ) const  return access(std::forward<F>(f)); 
  template<class F>
  auto write( F&& f )  return access(std::forward<F>(f)); 

  using base::base;
protected:
  using base::access;
  template<class F>
  auto access( F&& f ) const 
    auto l = lock();
    return std::forward<F>(f)(this->t);
  
  using base::lock;
  auto lock() const  return std::shared_lock<M>(this->m); 
;

【讨论】:

【参考方案3】:

std::shared_mutex 在数据结构(如 DNS 缓存)很少更新的情况下尤其有用。使用std::mutex 保护数据结构可能过于悲观,因为它消除了读取数据结构时可能出现的并发 当它不进行修改时。多个线程可以同时在同一个std::shared_mutex 上拥有一个共享锁。

Anthony Williams 书中的一个例子:

class dns_cache

    std::map<std::string,dns_entry> entries;
    mutable boost::shared_mutex entry_mutex;

public:

    dns_entry find_entry(std::string const& domain) const
    
        boost::shared_lock<boost::shared_mutex> lk(entry_mutex);
        std::map<std::string,dns_entry>::const_iterator const it = entries.find(domain);
        return (it==entries.end()) ? dns_entry() : it->second;
    

    void update_or_add_entry(std::string const& domain,
                            dns_entry const& dns_details)
    
        std::lock_guard<boost::shared_mutex> lk(entry_mutex);
        entries[domain] = dns_details;
    
;

这里,函数find_entry 基本上执行读取操作,而update_or_add_entry 执行写入操作。

所以,可以说std::shared_mutex 是一个典型的读写器互斥体,因为它允许 两种不同的使用方式:单个“编写器”线程的独占访问或共享访问, 多个“阅读器”线程的并发访问。

【讨论】:

以上是关于std::mutex 和 std::shared_mutex 之间的区别的主要内容,如果未能解决你的问题,请参考以下文章

我可以在 std::shared_mutex 上使用 std::shared_lock 更改数据吗?

C++关于锁的总结

boost的线程库和c++11的线程序有何不同

锁前后检查资源

C++-mutex(待验证)

在 jemalloc.h 之前包含 std::mutex 时编译错误