web-dev-qa-db-ja.com

std :: mutexとstd :: shared_mutexの違い

std::shared_mutexC++17を見つけました。 std::shared_mutexとは正確に何で、std::mutexとどのように違うのですか?

12
asad_nitp

ドキュメント で述べたように

Shared_mutexクラスは、複数のスレッドが同時にアクセスすることから共有データを保護するために使用できる同期プリミティブです。排他的アクセスを容易にする他のmutexタイプとは対照的に、shared_mutexには2つのアクセスレベルがあります。

  • 共有-複数のスレッドが同じミューテックスの所有権を共有できます
  • 排他的-mutexを所有できるスレッドは1つだけです。

共有ミューテックスは通常、複数のリーダーがデータ競合を引き起こさずに同時に同じリソースにアクセスできるが、1人のライターだけがアクセスできる状況で使用されます。

これにはさまざまな使用法がありますが、1つの一般的な使用法は Read Write Lock を実装することです。この場合、複数のスレッドが共有データを読み取ることができますが、常に1つのスレッドのみが排他的に書き込みます。したがって、複数のリーダーがある場合、ミューテックスは「共有モード」で動作しますが、書き込みが要求されると、「排他モード」に変わります。

12
CoryKramer

mutexはロックされているか、ロックされていません。

A shared_mutexは排他的にロックされているか、共有されているか、またはロックされていません。

クライアントはいくつでも共有ミューテックスを共有ロックできます。

誰かが排他的にロックしている場合、他の誰もanyロックを保持できません。

Windowsでは、これはSWRLOCKタイプです。実際、このロックは通常、読み取り/書き込みロックを実装するために使用されます。多くの読者が許可されていますが、書き込みは排他的でなければなりません。

以下は、共有ミューテックスと非共有ミューテックスの2つのテンプレートラッパーを作成するサンプルコードです。あるケースでは、異なるロックを取得する読み取り操作と書き込み操作があります。もう1つは、アクセス権があるだけです。

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); }
};

std::shared_mutexは、データ構造(DNSキャッシュなど)がまれに更新されるになる場合に特に役立ちます。 std::mutexを使用してデータ構造を保護すると、データ構造を変更していないときにデータ構造を読み取る際の同時実行性がなくなるため、過度に悲観的になる可能性があります。複数のスレッドが同じstd::shared_mutexを同時に共有ロックすることができます。

アンソニー・ウィリアムスの本からのそのような一例:

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は典型的なリーダーライターミューテックスであると言えます。単一の「書き込み」スレッド、または複数の「読み取り」スレッドによる共有同時アクセス。

1
Saurav Sahu