受け入れられた答えは誤解を招く可能性があると思うので、この答えを追加するだけです。すべてのケースでは、あなたが前notify_one()を呼び出すには、ミューテックスをロックする必要がありますどこかにあなたのコードは、スレッドセーフであるためにあなたが通知を呼び出す前に、実際にそれを再びアンロックかもしれませんが、_ *()。
明確にするために、wait()はlkのロックを解除し、ロックがロックされていない場合は未定義の動作になるため、wait(lk)に入る前にロックを取得する必要があります。これはnotify_one()の場合ではありませんが、あなたはあなたがコールすることはありませんようにする必要があり知らせる_ *()wait()を入力する前とmutexのロック解除そのコールを持ちます。これは明らかに、notify _ *()を呼び出す前に同じミューテックスをロックすることによってのみ実行できます。
たとえば、次の場合を考えてみます。
std::atomic_int count;
std::mutex cancel_mutex;
std::condition_variable cancel_cv;
void stop()
{
if (count.fetch_sub(1) == -999)
cv.notify_one();
}
bool start()
{
if (count.fetch_add(1) >= 0)
return true;
stop();
return false;
}
void cancel()
{
if (count.fetch_sub(1000) == 0)
return;
std::unique_lock<std::mutex> lk(cancel_mutex);
cancel_cv.wait(lk);
}
警告:このコードにはバグが含まれています。
考え方は次のとおりです。スレッドはstart()とstop()をペアで呼び出しますが、start()がtrueを返した場合に限ります。例えば:
if (start())
{
stop();
}
ある時点で1つの(他の)スレッドがcancel()を呼び出し、cancel()から戻った後、「Dostuff」で必要なオブジェクトを破棄します。ただし、start()とstop()の間にスレッドがある間、cancel()は戻らないはずであり、cancel()が最初の行を実行すると、start()は常にfalseを返すため、新しいスレッドは 'Doに入ることができません。スタッフのエリア。
正しく動作しますか?
理由は次のとおりです。
1)いずれかのスレッドがstart()の最初の行を正常に実行した場合(したがってtrueを返します)、cancel()の最初の行を実行したスレッドはまだありません(スレッドの総数は1000よりはるかに少ないと想定しています仕方)。
2)また、スレッドがstart()の最初の行を正常に実行したが、stop()の最初の行はまだ実行していない場合、どのスレッドもcancel()の最初の行を正常に実行することはできません(1つのスレッドのみに注意してください) cancel())を呼び出す:fetch_sub(1000)によって返される値は0より大きくなります。
3)スレッドがcancel()の最初の行を実行すると、start()の最初の行は常にfalseを返し、start()を呼び出すスレッドは「Dostuff」領域に入りません。
4)start()とstop()の呼び出し回数は常にバランスが取れているため、cancel()の最初の行の実行に失敗した後、stop()の(最後の)呼び出しによってカウントが発生する瞬間が常にあります。 -1000に到達するため、notify_one()が呼び出されます。キャンセルの最初の行でそのスレッドが失敗した場合にのみ発生する可能性があることに注意してください。
非常に多くのスレッドがstart()/ stop()を呼び出しているため、カウントが-1000に到達せず、cancel()が返されないという飢餓の問題とは別に、「ありそうもない、長くは続かない」と認められる可能性があります。別のバグがあります。
'Do stuff'領域内に1つのスレッドがある可能性があります。たとえば、stop()を呼び出しているだけです。その時点で、スレッドはcancel()の最初の行を実行し、fetch_sub(1000)を使用して値1を読み取り、フォールスルーします。ただし、ミューテックスを取得したり、wait(lk)を呼び出す前に、最初のスレッドはstop()の最初の行を実行し、-999を読み取り、cv.notify_one()を呼び出します。
次に、このnotify_one()の呼び出しは、条件変数でwait()を実行する前に実行されます。そして、プログラムは無期限にデッドロックします。
このため、wait()を呼び出すまでnotify_one()を呼び出すことはできません。条件変数の威力は、ミューテックスをアトミックにロック解除し、notify_one()の呼び出しが発生したかどうかを確認し、スリープ状態になるかどうかにあることに注意してください。あなたはそれをだますことはできませんが、やるあなたがfalseからtrueに条件を変更して、可能性がある変数に変更を加えるたびミューテックスをロックしておく必要性を保つため、ここで説明したようにあるため、競合状態のnotify_one()を呼び出している間、それはロックされています。
ただし、この例では条件はありません。条件として使用しなかったのはなぜですか 'count == -1000'?ここではまったく面白くないので、-1000に達するとすぐに、「Dostuff」領域に新しいスレッドが入ることはないと確信しています。さらに、スレッドは引き続きstart()を呼び出すことができ、カウントをインクリメントします(-999や-998など)が、それについては気にしません。重要なのは、-1000に到達したことだけです。これにより、「Dostuff」領域にスレッドがもうないことが確実にわかります。これはnotify_one()が呼び出されている場合に当てはまると確信していますが、cancel()がミューテックスをロックする前にnotify_one()を呼び出さないようにするにはどうすればよいですか?もちろん、notify_one()の直前にcancel_mutexをロックするだけでは役に立ちません。
問題は、条件を待っていないにもかかわらず、条件が残っているため、ミューテックスをロックする必要があることです。
1)その条件に達する前2)notify_oneを呼び出す前。
したがって、正しいコードは次のようになります。
void stop()
{
if (count.fetch_sub(1) == -999)
{
cancel_mutex.lock();
cancel_mutex.unlock();
cv.notify_one();
}
}
[...同じstart()...]
void cancel()
{
std::unique_lock<std::mutex> lk(cancel_mutex);
if (count.fetch_sub(1000) == 0)
return;
cancel_cv.wait(lk);
}
もちろん、これはほんの一例ですが、他の場合も非常に似ています。条件変数を使用するほとんどすべての場合、notify_one()を呼び出す前にそのミューテックスを(すぐに)ロックする必要があります。そうでない場合は、wait()を呼び出す前に呼び出すことができます。
この場合、notify_one()を呼び出す前にミューテックスのロックを解除したことに注意してください。そうしないと、notify_one()を呼び出すと、条件変数を待機しているスレッドがウェイクアップし、ミューテックスを取得しようとする可能性があります。ミューテックスを再度解放する前に、ブロックします。それは必要以上に少し遅いです。
この例は、条件を変更する行がwait()を呼び出す同じスレッドによって実行されるという点でちょっと特別でした。
より一般的なのは、あるスレッドが条件が真になるのを単に待機し、別のスレッドがその条件に関係する変数を変更する前にロックを取得する場合です(条件が真になる可能性があります)。その場合、条件が真になる直前(および直後)にミューテックスがロックされます。その場合、notify _ *()を呼び出す前にミューテックスのロックを解除するだけで問題ありません。
wait morphing
最適化を有効にするため)このリンクで説明されている経験則:より予測可能な結果を得るには、スレッドが2つを超える状況ではWITHロックを通知する方が適切です。