2017-02-26 8 views
5

私は基本的に、それはこのようになり、std::condition_variable(lock,pred)のVC++の実装に見てきました:std :: condition_variableで可能な競合状態ですか?

template<class _Predicate> 
     void wait(unique_lock<mutex>& _Lck, _Predicate _Pred) 
     { // wait for signal and test predicate 
     while (!_Pred()) 
      wait(_Lck); 
     } 

基本的には、cond->_get_cv()->wait(cs);(これらのすべてはしているが呼び出されますdo_waitを呼び出す_Cnd_waitを呼び出し裸wait通話_Cnd_waitXファイルcond.c)。

cond->_get_cv()Concurrency::details::stl_condition_variable_interfaceを返します。私たちは、ファイルprimitives.hに行けば

は、我々は、Windows 7と上記の下で、私たちは古き良きwin32のCONDITION_VARIABLE、およびwait通話__crtSleepConditionVariableSRWを含むクラスstl_condition_variable_win7を持っていることがわかります。

アセンブリのデバッグを少し行うと、__crtSleepConditionVariableSRWは、SleepConditionVariableSRW関数ポインタを抽出して呼び出します。

私の知る限り、win32 CONDITION_VARIABLEはカーネルオブジェクトではなく、ユーザーモードのものです。したがって、あるスレッドがこの変数に通知し、スレッドが実際にスリープしていない場合、通知が失われ、タイムアウトに達するか、他のスレッドが通知するまでスレッドはスリープ状態を維持します。小さなプログラムが実際にそれを証明することができます - あなたが通知のポイントを見逃しても、あなたのスレッドは他のスレッドが通知してもスリープ状態のままです。

私の質問は次のようになります。
条件変数で1つのスレッドが待機し、述部がfalseを返します。次に、上で説明したコール全体が実行されます。その時、別のスレッドが環境を変更して述語が真を返すようにが条件変数を通知します。私たちは元のスレッドで述語を渡しましたが、まだSleepConditionVariableSRWには入りませんでした。コールチェーンが非常に長くなりました。

したがって、我々は条件変数を通知しましたが、条件変数に置かれた述語は確かにtrueを返すでしょう(notifierが作ったので)、私たちは依然として永遠に条件変数をブロックしています。

これはどのように動作するのですか?それは起こるのを待っている醜い競争状態のように思えます。条件変数に通知し、述語がtrueを返す場合、スレッドはブロックを解除する必要があります。しかし、述語をチェックしてから眠るまでの間にぼやけていると、私たちは永遠にブロックされます。 std::condition_variable::waitはアトミック関数ではありません。

標準はそれについて何と言っていますか?それは本当に競合状態ですか?

答えて

2

あなたはすべての賭けが無効になるように契約に違反しました。参照:http://en.cppreference.com/w/cpp/thread/condition_variable

TLDR:あなたがミューテックスを保持している間、述語が他の誰かによって変更することは不可能です。あなたはstd::condition_variable::waitを呼び出す前に、そのミューテックスを取得する必要があります(両方waitリリースするので、ミューテックス、およびそれが契約だから)ミューテックスを保持しながら

はあなたが述語の基本的な変数を変更することになっています。変更を説明したシナリオでは

while (!_Pred())は、述語が保持していないことを見たが、wait(_Lck)前に、ミューテックスを解放する機会があった後が起こりました。これは、mutexを保持せずに述語がチェックするものを変更したことを意味します。あなたはルールに違反し、競争条件や無限の待機は、あなたが得ることができる最悪の種類のUBではありません。あなたはルールでプレイする場合は、少なくともこれらのいずれかで、...

ローカルおよびエラーを見つけることができますあなたがそう違反したルールに関連しています:

  1. ウェイターが
  2. 最初のミューテックスのホールドを取ります
  3. std::condition_variable::waitになります。 (通知者はまだミューテックスを待つことを思い出してください。)
  4. 述語をチェックし、それが保持していないことを確認します。 (を呼び出すと、まだがmutexを待つ)
  5. mutexをリリースして待機するために実装定義の魔法を呼び出す。
  6. 通知者は最終的にミューテックスを取ることができました。
  7. 通知者は、述部がtrueを保持するために変更が必要なものを変更します。
  8. 通知者はstd::condition_variable::notify_oneを呼び出します。

か:

  1. 通知は、ミューテックスを取得します。
  2. 通知者は、述語がtrueを保持するために変更が必要なものを変更します。 (ウェイターがまだブロックされていることを思い出してください)
  3. 通知者はミューテックスを解放します。 (途中でウェイターがstd::condition_variable::notify_oneに電話をかけますが、mutexが解放されたら...)
  4. ウェイターはミューテックスを取得します。
  5. ウェイターはstd::condition_variable::waitを呼び出します。
  6. ウェイターは、while (!_Pred())ビオラをチェックします。述語が真です。
  7. ウェイターはwaitの内部に入っていないので、std::condition_variable::notify_oneを呼び出すことができたかどうか、またはそれをまだ管理していないかどうかは関係ありません。
  8. cppreference.com上の要件の根拠だ

共有変数は、それが正しく待機中のスレッドに変更を公開するために、ミューテックスの下に変更する必要があり、原子である場合であっても。

これは条件変数ではなく、(などのWindows CONDITION_VARIABLE S、POSIX pthread_cond_t S、を含む)std::condition_variablesのための特別な要件については、一般的なルールであることに注意してください。


、発信者が偽のウェイクアップに対処する必要がないように、述語を取るwait過負荷がちょうど便利な機能であることを思い出してください。標準(§30.5.1/ 15)は、明示的に、このオーバーロードは、Microsoftの実装ではwhileループと等価であることを言う:

効果:相当

while (!pred()) 
    wait(lock); 

はシンプルをいwait作品ですか? waitを呼び出す前後に述語をテストしますか?すばらしいです。あなたは同じことをしています。またはvoid std::condition_variable::wait(std::unique_lock<std::mutex>& lock);も質問していますか?


Windows重要なセクションとスリムリーダー/ライターロックは、ユーザーモードの施設ではなく、カーネルオブジェクトであること質問への軽微とは無関係です。代わりの実装があります。 WindowsがCS/SRWLをアトミックにリリースして待機状態に入る方法を知りたい場合は、別の質問です.MutexesとEventsの単純なVistaユーザーモード実装は間違っていました。

+0

aha、私は "共有変数がアトミックであっても..."ということを忘れていました。これは私が心に留めていたものです。 –

+0

@DavidHaim:これはcppreferenceのヒントです。スタンダードには何の言い方もない。 'std :: condition_variable'(一般に条件変数と同様に)は、待ちと通知との間の関係を作成し、mutexを保持します。そのような方法でmutexを取得する正当な理由があると想定されます。か否か。そのミューテックスと何をするかはあなたのビジネスです。条件変数が実際に行う唯一のことは、ミューテックスを解放し、待ち状態* atomically *に入ることです。 「条件」の部分は完全にあなた次第です。 – conio

関連する問題