std::shared_mutex
でC++17
を見つけました。 std::shared_mutex
とは正確に何で、std::mutex
とどのように違うのですか?
ドキュメント で述べたように
Shared_mutexクラスは、複数のスレッドが同時にアクセスすることから共有データを保護するために使用できる同期プリミティブです。排他的アクセスを容易にする他のmutexタイプとは対照的に、shared_mutexには2つのアクセスレベルがあります。
- 共有-複数のスレッドが同じミューテックスの所有権を共有できます。
- 排他的-mutexを所有できるスレッドは1つだけです。
共有ミューテックスは通常、複数のリーダーがデータ競合を引き起こさずに同時に同じリソースにアクセスできるが、1人のライターだけがアクセスできる状況で使用されます。
これにはさまざまな使用法がありますが、1つの一般的な使用法は Read Write Lock を実装することです。この場合、複数のスレッドが共有データを読み取ることができますが、常に1つのスレッドのみが排他的に書き込みます。したがって、複数のリーダーがある場合、ミューテックスは「共有モード」で動作しますが、書き込みが要求されると、「排他モード」に変わります。
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
は典型的なリーダーライターミューテックスであると言えます。単一の「書き込み」スレッド、または複数の「読み取り」スレッドによる共有同時アクセス。