[解決済み] condition_variable.notify_one()を呼び出す前にロックを取得する必要がありますか?
質問
の使い方について少し混乱しています。
std::condition_variable
. 私は
unique_lock
の上に
mutex
を呼び出す前に
condition_variable.wait()
. を呼び出す前にユニークロックを取得する必要があるかどうかについては、見つけることができませんでした。
notify_one()
または
notify_all()
.
の例 cppreference.com は矛盾しています。例えば notify_one page はこのような例をあげています。
#include <iostream>
#include <condition_variable>
#include <thread>
#include <chrono>
std::condition_variable cv;
std::mutex cv_m;
int i = 0;
bool done = false;
void waits()
{
std::unique_lock<std::mutex> lk(cv_m);
std::cout << "Waiting... \n";
cv.wait(lk, []{return i == 1;});
std::cout << "...finished waiting. i == 1\n";
done = true;
}
void signals()
{
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "Notifying...\n";
cv.notify_one();
std::unique_lock<std::mutex> lk(cv_m);
i = 1;
while (!done) {
lk.unlock();
std::this_thread::sleep_for(std::chrono::seconds(1));
lk.lock();
std::cerr << "Notifying again...\n";
cv.notify_one();
}
}
int main()
{
std::thread t1(waits), t2(signals);
t1.join(); t2.join();
}
ここでは、ロックは最初の
notify_one()
にはかかりませんが、2つ目の
notify_one()
. 他のページの例を見てみると、さまざまなことがわかりますが、ほとんどはロックを取得していません。
-
を呼び出す前に、自分自身で mutex をロックすることを選択することはできますか?
notify_one()
を呼び出す前にミューテックスをロックすることを自分で選択できますか、そしてなぜそれをロックすることを選択するのでしょうか? -
与えられた例で、なぜ最初の
notify_one()
にはロックがなく、それ以降の呼び出しにはロックがあるのはなぜでしょうか。この例は間違っているのでしょうか、それとも何か根拠があるのでしょうか。
どのように解決するのですか?
を呼び出す際に、ロックを保持する必要はありません。
condition_variable::notify_one()
を呼び出すときにロックを保持する必要はありませんが、それはまだよく定義された動作であり、エラーではないという意味で間違いではありません。
しかし、待機中のスレッドが実行可能になれば (もしあれば) すぐに通知スレッドが保持するロックを取得しようとするので、これは "pessimization" であるかもしれません。を呼び出している間は、条件変数に関連付けられたロックを保持しないようにするのが良い経験則になると思います。
notify_one()
または
notify_all()
. 参照
Pthread Mutex: pthread_mutex_unlock() は多くの時間を消費します。
の pthread 相当のものを呼ぶ前にロックを解放する例については、pthread_mutex_unlock() を参照してください。
notify_one()
を呼び出す前にロックを解放することで、パフォーマンスが大幅に改善されました。
を覚えておいてください。
lock()
の呼び出しは
while
ループの間にロックを保持する必要があるためです。
while (!done)
ループの条件チェックの間、ロックを保持する必要があるからです。というのは、notify_one()
.
2016-02-27
: レースコンディションが存在するかどうかについてのコメントでいくつかの質問に対処するための大規模な更新は、ロックは、そのためのヘルプではありません。
notify_one()
を呼び出します。この質問は 2 年近く前にされたものなので、この更新は遅いと思いますが、@Cookie さんの質問である、プロデューサー (
signals()
この例では) が
notify_one()
の直前にコンシューマ(
waits()
この例では) が
wait()
.
重要なのは
i
- というオブジェクトがどうなるかということです。 このオブジェクトは
condition_variable
への変更を効率的に待つためのメカニズムに過ぎません。
i
.
を更新するとき、プロデューサーはロックを保持する必要があります。
i
をチェックするとき、コンシューマはロックを保持する必要があります。
i
をチェックし
condition_variable::wait()
(を呼び出すことです(もし待つ必要があるのなら)。この場合、重要なのは
ロックを保持しているのと同じインスタンスでなければならないということです。
(しばしばクリティカル・セクションと呼ばれます) であることです。クリティカルセクションはプロデューサーが更新するときに保持されるので
i
を更新したときと、コンシューマがこのセクションをチェック&ウェイトするときです。
i
をチェック&ウェイしているときは
i
をチェックする間に変化することはありません。
i
をチェックするときと
condition_variable::wait()
. これが条件変数を正しく使うためのポイントです。
C++標準では、(今回のように)述語で呼ばれた場合、condition_variable::wait()は以下のような挙動を示すとされています。
while (!pred())
wait(lock);
コンシューマがチェックする際に発生しうる状況は2つあります。
i
:
-
もし
i
が 0 ならば、コンシューマはcv.wait()
を呼び出します。i
が 0 のままであればwait(lock)
の部分が呼び出されたときにも、0 のままです。 この場合、プロデューサにはcondition_variable::notify_one()
を呼び出す機会がありません。while
を呼び出した後までループします。cv.wait(lk, []{return i == 1;})
(そしてwait()
の呼び出しは、notify を適切に「捕捉」するために必要なことをすべて行っています -。wait()
はそれが完了するまでロックを解放しません)。 したがって、この場合、消費者は通知を見逃すことはできません。 -
もし
i
がすでに 1 である場合、コンシューマはcv.wait()
を呼び出したとき、そのwait(lock)
の部分は決して呼び出されないので、実装のwhile (!pred())
テストが内部ループを終了させるためです。 この状況では、notify_one() の呼び出しがいつ発生するかは問題ではなく、コンシューマはブロックされません。
この例では、さらに複雑な要素として
done
変数を使ってプロデューサー・スレッドにシグナルを送り、 コンシューマーが
i == 1
へのアクセスは、すべて
done
へのアクセス (読み込みと修正の両方) は、同じ重要なセクションで
i
と
condition_variable
.
@eh9さんが指摘された質問を見ると。 std::atomic と std::condition_variable を使用すると同期が信頼できない を見ると、あなたは は はレースコンディションを参照してください。しかし、その質問で投稿されたコードは、条件変数を使用する際の基本的なルールの1つを破っています。 それは、チェック アンド ウェイトを実行するときに、単一のクリティカル セクションを保持しないことです。
その例では、コードは次のようになります。
if (--f->counter == 0) // (1)
// we have zeroed this fence's counter, wake up everyone that waits
f->resume.notify_all(); // (2)
else
{
unique_lock<mutex> lock(f->resume_mutex);
f->resume.wait(lock); // (3)
}
このとき
wait()
を押しながら、#3 の
f->resume_mutex
. しかし、そのチェックは
wait()
が必要であるかどうかのチェックは、ステップ#1 で
ではなく
は、そのロックを保持している間は全く行われません(ましてや、チェックアンドウェイトのために継続的に行われることはありません)。 そのコードスニペットで問題を抱えている人は、以下のように考えたのだと思います。
f->counter
が
std::atomic
型であれば、要件を満たすことができます。しかし
std::atomic
が提供するアトミック性は、その後に呼ばれる
f->resume.wait(lock)
. この例では、以下のような競合が発生しています。
f->counter
がチェックされたとき (ステップ #1) と
wait()
が呼び出されたとき(ステップ#3)。
この質問の例では、そのレースは存在しません。
関連
-
[解決済み】coutはstdのメンバではない
-
[解決済み】getline()が何らかの入力の後に使用されると動作しない 【重複あり
-
[解決済み】C++コンパイルタイムエラー:数値定数の前に期待される識別子
-
[解決済み】「std::operator」で「operator<<」にマッチするものがない。
-
[解決済み】Visual Studio 2013および2015でC++コンパイラーエラーC2280「削除された関数を参照しようとした」が発生する
-
[解決済み】オブジェクト引数のない非静的メンバ関数の呼び出し コンパイラーエラー
-
[解決済み] [Solved] インクルードファイルが開けません。'stdio.h' - Visual Studio Community 2017 - C++ Error
-
[解決済み] gdbを使用してもデバッグシンボルが見つからない
-
[解決済み] 警告:暗黙の定数変換でのオーバーフロー
-
[解決済み] 揮発性 vs. 連動性 vs. ロック性
最新
-
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 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み】非静的メンバ関数への参照を呼び出す必要がある
-
[解決済み】識別子 "string "は未定義?
-
[解決済み] エラーが発生する。ISO C++は型を持たない宣言を禁じています。
-
[解決済み】 != と =! の違いと例(C++の場合)
-
[解決済み】テンプレートの引数1が無効です(Code::Blocks Win Vista) - テンプレートは使いません。
-
[解決済み】デバッグアサーションに失敗しました。C++のベクトル添え字が範囲外
-
[解決済み】cc1plus:エラー:g++で認識されないコマンドラインオプション"-std=c++11"
-
[解決済み】C++ - 適切なデフォルトコンストラクタがない [重複]。
-
[解決済み] 数値定数の前にunqualified-idを付けて、数値を定義することを期待する。
-
[解決済み】デバッグアサーションに失敗しました