C ++ 11スレッドライブラリ(6)条件変数

条件変数は、複数のスレッドが相互に通信できるようにするスレッド同期プリミティブです。
彼はいくつかのスレッドを待機させることができ(おそらくタイムアウト)、別のスレッドが継続実行通知を発行する可能性があり、条件変数は常にミューテックスに関連しています。———— Cpprefによる条件変数の説明

1.なぜ条件変数が必要なのですか?

簡単に言うと、条件変数は、ミューテックスが特定の状況で(効率的に)実行できないタスクを実行できます。これは、実行を開始する前に別のスレッドが特定の要件を満たすまでスレッドをブロックするのと似ています。ミューテックスロックを使用してこのようなプロセスを実装する場合、

2.条件変数のC ++サポート

#include <condition_variable>

  • condition_variable(C ++ 11)は、unique_lockタイプの条件変数(クラス)を提供します
  • condition_variable_any(C ++ 11)任意のロックタイプの条件変数(クラス)を提供します
  • notify_all_at_thread_exit(C ++ 11)スレッドが完全に終了したときにnotify_allへの呼び出しを計画します(関数)
  • cv_status(C ++ 11)条件変数で時限待機を使用する場合、可能なすべての(time_outまたはno_timeout)結果(列挙型)を列挙します

3つ目は、条件変数condition_variable

3.1はじめに

条件変数は、他のスレッドが共有条件を変更し、条件変数に通知することを認識して、1つ以上のスレッドを同時にブロックするために使用できる同期プリミティブです。

スレッドが共有変数を変更しようとすると、次のことを行う必要があります。

  • std :: mutexを取得します(最も一般的なのはstd :: lock_guradを使用することです)
  • ロックが取得されたときに変更します
  • 条件変数でnotify_oneまたはnotify_allを呼び出します(このロックを最初に取得する必要はありません)

共有変数がアトミック型であっても、ミューテックスの下で変更し、待機中のスレッドに正しく公開する必要があることに注意してください。???

std :: condition_variableを待機する予定のスレッドは、次のことを行う必要があります。

  • 共有変数の保護に使用されるのと同じミューテックスでstd :: unique_lockstd :: mutexを取得
    するには、次のいずれかを実行します。
  • 状態が更新されているか、通知されているかを確認してください
  • wait、wait_for、またはwait_untilを実行すると、待機操作によってミューテックスが自動的に解放され、スレッドの実行が一時停止されます。
  • condition_variableが通知されると、制限時間がなくなるか、誤ったウェイクアップが発生し、スレッドがウェイクアップされ、相互排他が自動的に回復します。その後、スレッドは状態をチェックし、ウェイクアップがfalseの場合は待機を続ける必要があります。あなたはあまりにも多くのトラブルをしたくない場合は、使用することができwaitwait_forそしてwait_untilあなたは、三つの上に書き込みの簡素化手続きを完了することができ、述語関数のオーバーロード。

std :: condition_variableは、std :: unique_lockstd :: mutexでのみ使用できます。この制限により、このメカニズムは一部のプラットフォームで最高の効率を達成できます。もちろん、ロックの種類とは関係のない条件変数を使用したい場合は、彼の兄弟を試すことができます:: std::condition_variable_any

3.2メンバー関数

コンストラクターには、パラメーターのデフォルトコンストラクター、コピー、割り当てはありません。残りは2種類のメンバー関数で、1つは通知メソッド、もう1つは待機メソッドです。

通知方法:全員notify_oneに通知するか、1人に通知しnotify_allます。

待機方法:待機を待機し、一定時間wait_forを待機し、特定の瞬間を待機します。wait_until。使用されるメソッドは、「条件変数が特定のメソッドを呼び出して、unique_lcokを待機する」です。

cond.wait(ulo);

waitメソッドを呼び出すと、条件変数が通知されるか、誤ったウェイクアップが発生するまで現在のスレッドがブロックされ、オプションで特定の述語が満たされるまでループします。誤ったウェイクアップを回避するために、述語を使用できます。

template< class Predicate >
void wait( std::unique_lock<std::mutex>& lock, Predicate pred );

これは、次のステートメントと同等です。

while (!pred()) {
    
    //不满足继续等待
    wait(lock);
}

この述語は、接触ブロック条件として理解する必要があります。

3つ目は、条件変数インスタンスです。

この例では、並行スレッドの順次印刷を実装します。

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
using namespace std;

class ConditionVarTest
{
    
    
public:
    void printone();
    void printtwo();
    void printthree();
    mutex lo;
    condition_variable cond;
    int i=0;
};

void ConditionVarTest::printone(){
    
    
    cout<<"1";
    i=1;
    cond.notify_all();
}
 
void ConditionVarTest::printtwo(){
    
    
    unique_lock<mutex> ulo(lo);
    cond.wait(ulo,[this]{
    
    return i==1;});
    cout<<"2";
    i=2;
    cond.notify_one();
}

void ConditionVarTest::printthree(){
    
    
    unique_lock<mutex> ulo(lo);
    cond.wait(ulo,[this]{
    
    return i==2;});
    cout<<"3";

}

int main()
{
    
    
    ConditionVarTest a;
    thread th1(&ConditionVarTest::printone,&a);
    thread th2(&ConditionVarTest::printtwo,&a);
    thread th3(&ConditionVarTest::printthree,&a);
    
    
    th1.join();
    th2.join();
    th3.join();
    
    return 0;
    
}

四、誤った目覚め

条件変数は、生産と消費の設計モデルによく見られる誤ったウェイクアップも注意を払う必要があります

  • コンシューマーの数(CONSUMER_NUM)が1の場合、他のスレッドがキューをめぐって競合することはなく、誤ったウェイクアップはトリガーされません。
  • プロデューサーの数(
    PRODUCTER_NUM )が1の場合、notify_oneを使用してコンシューマースレッドに通知すると、誤ったウェイクアップは発生しません。これは、1つのコンシューマースレッドのみがシグナルを受信して​​ウェイクアップするたびに、製品が消費されています新しい信号があります。
    notify_allを使用してコンシューマスレッドに通知すると、誤ったウェイクアップが発生し、シグナルを受信した後に複数のコンシューマスレッドがウェイクアップされます。スレッドがウェイクアップされる前に、他のスレッドが最初にウェイクアップされてロックを保持し、製品を消費する場合があります。
  • プロデューサーの数(PRODUCTER_NUM)が1より大きい場合、notify_oneまたはnotify_allのいずれかが誤ってウェイクアップされます。複数のプロデューサーがnotify_oneを使用すると、複数のスレッドがウェイクアップされます。そのうちの1つが非常に高速に処理される可能性があります。すべてのデータが処理されると、次にウェイクアップされるスレッドには処理するデータがありません

誤った目覚めに対処するために、消費者は、目覚めた後、製品の数を繰り返しチェックするためにwhileを使用します。なぜwhileを使用するのですか?シングルコアでbroadcast()を使用すると、スリリングなグループ効果が発生し、誤った目覚めが発生しますが、notify()を使用しても、明らかに誤った目覚めは発生しません(特定のスレッドのみが目覚めます)。ただし、マルチコアプロセッサでは、pthread_cond_signalは、条件変数でブロックされた複数のスレッドをアクティブ化する場合があります(マルチコアの場合:通知時に複数のスレッドが同時に起動されます)。その結果、スレッドがpthread_cond_signal()を呼び出すと、pthread_cond_wait()またはpthread_cond_timedwait()を呼び出す複数のスレッドが返されます。この効果を「ショックグループ効果」と呼びます。ifで判断すると、if条件が満たされた場合(false)に複数の待機スレッドが覚醒しますが、実際には条件が満たされていない場合、生産者が生産した消費財は最初のスレッドによって消費され、「false覚醒」が発生します。 "。したがって、誤ったウェイクアップを回避するために、状態を再判断するためにwhileを使用する必要があります。

[1] https://www.jianshu.com/p/01ad36b91d39
[2] https://blog.csdn.net/shizheng163/article/details/83661861

おすすめ

転載: blog.csdn.net/weixin_39258979/article/details/114199315