[解決済み] C++でmovable型のmutexをどのように扱えばいいですか?
質問
デザインで
std::mutex
は移動もコピーもできません。これは、クラス
A
はデフォルトの移動コンストラクタを受け取らないということです。
どのようにしてこの型を
A
をスレッドセーフな方法で移動可能にするにはどうしたらよいでしょうか?
どのように解決するのですか?
まずは、ちょっとしたコードから。
class A
{
using MutexType = std::mutex;
using ReadLock = std::unique_lock<MutexType>;
using WriteLock = std::unique_lock<MutexType>;
mutable MutexType mut_;
std::string field1_;
std::string field2_;
public:
...
C++11ではあまり利用されませんが、C++14ではより便利になる、示唆に富んだ型別名をいくつか入れました。 我慢してください、きっとうまくいきます。
あなたの質問は以下のことに集約されます。
このクラスの移動コンストラクタと移動代入演算子はどのように書けばよいのでしょうか?
まずは移動コンストラクタからです。
移動コンストラクタ
なお、メンバ
mutex
が
mutable
. 厳密に言えば、これは移動メンバには必要ないのですが、コピーメンバも必要なのではと推測されます。 そうでない場合は、mutex を作る必要はありません。
mutable
.
を構成するとき
A
を構成する場合,ロックする必要はありません.
this->mut_
. しかし
mut_
をロックする必要があります。 これは次のように行うことができます。
A(A&& a)
{
WriteLock rhs_lk(a.mut_);
field1_ = std::move(a.field1_);
field2_ = std::move(a.field2_);
}
のメンバをデフォルトで構築しなければならないことに注意してください。
this
のメンバを最初に構築し、値を割り当てるのは
a.mut_
がロックされた後に値を割り当てます。
移動の割り当て
移動代入演算子は、他のスレッドが代入式のlhsまたはrhsのいずれかにアクセスしているかどうかわからないため、かなり複雑です。 そして一般的に、次のようなシナリオから保護する必要があります。
// Thread 1
x = std::move(y);
// Thread 2
y = std::move(x);
上記のシナリオを正しくガードする移動代入演算子は以下の通りです。
A& operator=(A&& a)
{
if (this != &a)
{
WriteLock lhs_lk(mut_, std::defer_lock);
WriteLock rhs_lk(a.mut_, std::defer_lock);
std::lock(lhs_lk, rhs_lk);
field1_ = std::move(a.field1_);
field2_ = std::move(a.field2_);
}
return *this;
}
を使わなければならないことに注意してください。
std::lock(m1, m2)
を使ってロックしなければならないことに注意してください。 もし、1つずつロックしてしまうと、2つのスレッドが上記のように2つのオブジェクトを逆の順序で割り当てたときに、デッドロックが発生する可能性があります。 のポイントは
std::lock
のポイントは、そのデッドロックを回避することです。
コピーコンストラクタ
あなたはコピーメンバーについて尋ねませんでしたが、今話してもよいかもしれません(あなたでなくても、誰かがそれを必要とするでしょう)。
A(const A& a)
{
ReadLock rhs_lk(a.mut_);
field1_ = a.field1_;
field2_ = a.field2_;
}
copy コンストラクタは move コンストラクタと似ていますが
ReadLock
の代わりにエイリアスが使用されます。
WriteLock
. 現在、これらのエイリアスは両方とも
std::unique_lock<std::mutex>
であるため、実際には何の違いもありません。
しかし、C++14では、このように言うことができるようになります。
using MutexType = std::shared_timed_mutex;
using ReadLock = std::shared_lock<MutexType>;
using WriteLock = std::unique_lock<MutexType>;
これは は は最適化されるかもしれませんが、絶対ではありません。 そうであるかどうかを判断するには、測定する必要があります。 しかし、この変更により、コピー構成 から を同時に複数のスレッドで同じ rhs をコピー構築することができます。 C++11 のソリューションでは、rhs が変更されていないにもかかわらず、そのようなスレッドをシーケンシャルにすることを余儀なくされます。
コピー代入
完全を期すために、ここにコピー代入演算子を示します。これは他のすべてについて読んだ後、かなり自明なはずです。
A& operator=(const A& a)
{
if (this != &a)
{
WriteLock lhs_lk(mut_, std::defer_lock);
ReadLock rhs_lk(a.mut_, std::defer_lock);
std::lock(lhs_lk, rhs_lk);
field1_ = a.field1_;
field2_ = a.field2_;
}
return *this;
}
などなど。
その他、アクセスするメンバーやフリーファンクションがあれば
A
の状態にアクセスする他のメンバーやフリー関数も、 複数のスレッドが一度に呼び出すことができることを想定している場合は、 保護する必要があります。 例えば、以下のように
swap
:
friend void swap(A& x, A& y)
{
if (&x != &y)
{
WriteLock lhs_lk(x.mut_, std::defer_lock);
WriteLock rhs_lk(y.mut_, std::defer_lock);
std::lock(lhs_lk, rhs_lk);
using std::swap;
swap(x.field1_, y.field1_);
swap(x.field2_, y.field2_);
}
}
もし、単に
std::swap
に依存する場合、ロックは間違った粒度で行われ、ロックとアンロックは
std::swap
が内部的に実行する3つの動きの間のロックとアンロックという、誤った粒度でロックされることになります。
確かに
swap
について考えることは、quot;スレッドセーフのために提供する必要があるかもしれない API についての洞察を与えることができます。
A
これは一般的に、quot;ロック粒度の問題から、quot;スレッドセーフでないAPIとは異なるでしょう。
また、"self-swap" から保護する必要性にも注意してください。 "self-swap" は禁止されるべきものです。 自己チェックがなければ、同じミューテックスを再帰的にロックすることになります。 これはまた、セルフチェックなしで
std::recursive_mutex
を使用することで、セルフチェックなしで解決することもできます。
MutexType
.
更新
以下のコメントで、Yakk はコピーと移動のコンストラクタでデフォルトの構成をしなければならないことにかなり不満を抱いています (そして彼の指摘は的を射ています)。 この問題について、メモリを消費しても構わないほど強く感じるのであれば、このように回避することができます。
-
データメンバとして必要なロックタイプを追加します。 これらのメンバは、保護されるデータの前に来る必要があります。
mutable MutexType mut_; ReadLock read_lock_; WriteLock write_lock_; // ... other data members ...
-
そして、コンストラクタ(コピーコンストラクタなど)の中で、次のようにします。
A(const A& a) : read_lock_(a.mut_) , field1_(a.field1_) , field2_(a.field2_) { read_lock_.unlock(); }
おっと、Yakk は、私がこの更新を完了する前に彼のコメントを消去してしまいました。 しかし、彼はこの問題を推し進め、この回答で解決策を得たことは称賛に値します。
アップデート 2
そしてdypはこんな良い提案をしてくれました。
A(const A& a)
: A(a, ReadLock(a.mut_))
{}
private:
A(const A& a, ReadLock rhs_lk)
: field1_(a.field1_)
, field2_(a.field2_)
{}
関連
-
[解決済み】識別子 "string "は未定義?
-
[解決済み】C++エラーです。"配列は中括弧で囲まれたイニシャライザーで初期化する必要がある"
-
[解決済み】Visual Studio 2013および2015でC++コンパイラーエラーC2280「削除された関数を参照しようとした」が発生する
-
[解決済み] 関数からunique_ptrを返す
-
[解決済み] Intel CPU の _mm_popcnt_u64 で、32 ビットのループカウンターを 64 ビットに置き換えると、パフォーマンスが著しく低下します。
-
[解決済み】静的定数文字列(クラスメンバ)
-
[解決済み】コンストラクターからの例外のスローイング
-
[解決済み】Singleton。どのように使用するべきか
-
[解決済み] 再帰的ロック (Mutex) vs 非再帰的ロック (Mutex)
-
[解決済み] ロックされていないミューテックスをロックすることは、どの程度効率的ですか?ミューテックスのコストは?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン