3

スレッドがキューに同時にアクセスしないように、キューの安全のためにクリティカル セクションが必要です。このコードは、クリティカル セクションに関連する行にコメントを付けても機能します。誰でも理由を説明できますか?

queue<int> que;
CRITICAL_SECTION csection;
int i=0;

DWORD WINAPI ProducerThread(void*)
{

    while(1)
    {
        //if(TryEnterCriticalSection(&csection))
        {
            cout<<"Pushing value "<<i<<endl;
            que.push(i++);
            //LeaveCriticalSection(&csection);
        }
    }
}

//Consumer tHread that pops out the elements from front of queue
DWORD WINAPI ConsumerThread(void*)
{
    while(1)
    {
        //if(TryEnterCriticalSection(&csection))
        {
            if(!que.empty())
            {
                cout<<"Value in queue is "<<que.front()<<endl;
                que.pop();
            }
            else
                Sleep(2000);
            //LeaveCriticalSection(&csection);
        }
    }
}

int _tmain(int argc, _TCHAR* argv[])
{
    HANDLE handle[2];
    //InitializeCriticalSection(&csection);
    handle[0]=NULL;
    handle[1]=NULL;
    handle[0]=CreateThread(0,0,(LPTHREAD_START_ROUTINE)ProducerThread,0,0,0);
    if(handle[0]==NULL)
        ExitProcess(1);

    handle[1]=CreateThread(0,0,(LPTHREAD_START_ROUTINE)ConsumerThread,0,0,0);
    if(handle[1]==NULL)
        ExitProcess(1);

    WaitForMultipleObjects(2,handle,true,INFINITE);

    return 0;
}
4

4 に答える 4

2

あなたの特定のケースでは、cout は「get」よりも数百倍長くかかります。キューが空の場合はスリープします。これにより、「コンシューマー」スレッドがキューをフェッチする前に、他のスレッドが多くのキューをいっぱいにすることができます。

全速力で実行し (デバッグ出力なし、スリープなし)、長時間実行することを確認し、単純な計算で反対側の値を確認します。

このようなもの:

int old_val = val;
while(1)
{
    if(!que.empty())
    {
       int  val = que.front();

       que.pop();
       if (old_val+1 != val)
       {
          /// Do something as things have gone wrong!
       }
     }
}

これもすぐに/自明にうまくいかない可能性があることに注意してください。何時間も実行したいのですが、できればマシン上で他の何かを実行して、次のようなバッチファイルを実行する必要があります。

@echo off
:again 
dir c:\ /s > NUL:
goto again

[Windows 用のバッチ スクリプトを作成してからしばらく経ちました。100% 正しいとは限りませんが、私が間違っていた場合は、Google で答えを見つけられるはずです。マシンを「中断」するためのアイデアです。 ]。

また、ペアごとに別のキューを使用して、スレッドのペアの複数のコピーを実行してみてください。これにより、より多くのスケジューリング アクティビティが強制され、問題が発生する可能性があります。

アントンが言うように、これらのいくつかはしばしば再現が非常に困難です。キューが台無しになるリアルタイムOSで問題が発生しました.唯一の本当の兆候は、「ストレステスト」(いくつかの異なる割り込みソースを含む「ランダムな」ことを行います)中にメモリが最終的に不足したことでした. OS は実動テストで数百単位でテストされ、実際の実動システムとして現場​​に出されました [また、別のプロセッサで実行されている同じコードが世界中の電話交換機を操作していましたが、メモリ リークに関する顧客の苦情はありませんでした]。 、一見メモリリークなし!しかし、キュー処理の 1 つの「穴」は、1 つの関数で実行されるだけでした。ときどき列ができて変な状況になったのはストレステストそのものかと思ったら、

于 2013-01-28T13:31:44.250 に答える
2

これは、おそらく次の 2 つの理由で偶然に機能します。

  1. それは機能しませんが、決して気付かないでしょう。コンシューマーは、キューにあるもの、またはキューにあると思われるものをすべてプルします。何もない場合は、プロデューサーが何かをプッシュするまでスリープします。プロデューサーは最後にのみ追加するのに対し、コンシューマーは最初からのみ読み取るため、これは「機能」します。更新を除くsizesizeおそらく、最終的には、要素はあるがそれを反映していない状態のキューができます。これは厄介ですが、遅かれ早かれ発生する可能性が高い反対の場合は、さらに厄介です.
    あなたは知る方法がありません。キューに入れられた作業項目が何らかの理由で「消える」か、メモリが不足しているかどうかは最終的にわかるかもしれませんが、その理由を突き止めてみてください。
  2. クリティカル セクションによって内部的にロックされているprintf(または同じ) を使用します。この「種類」は、そうでない場合を除いて、std::cout必要な方法でキューへのアクセスをロックします。これは 99.9% の確率で動作します (コンシューマーが印刷しようとしてブロックされ、プロデューサーがキューに追加するよりもウェイクアップに時間がかかるため、偶然です)。ただし、印刷直後にコンテキストスイッチが発生すると、突然失敗します。バン、あなたは死んでいます。

クリティカル セクション オブジェクトまたはミューテックスを使用して、クリティカル コード セクションを保護することが絶対に必要です。それ以外の場合、結果は予測できません。そして、信じられていることとは反対に、「でもうまくいく」というのは良いことではなく、起こり得る最悪のことです。機能しなくなるまでしか機能せず、その後は理由がわからないからです。

とはいえ、IO 完了ポートを使用すると、すべての作業が非常に効率的に行われます。GetQueuedCompletionStatusを使用してポートから「イベント」を取得し、 PostQueuedCompletionStatusを使用してイベントを投稿できます。完了ポートは、複数のコンシューマーへの適切な同期を含むキューの処理全体を行います (そして、LIFO の順序でそれを行います。これは、コンテキストの切り替えとキャッシュの無効化を回避するのに有利です)。
各イベントにはOVERLAPPED構造体へのポインターが含まれていますが、完了ポートはそれを使用しません。任意のポインターを渡すことができます (または、その方が良い場合は、ポインターにOVERLAPPED続いて独自のデータを渡します)。

于 2013-01-28T13:33:48.763 に答える
0

CRITICAL_SECTIONこの種のバグが防止する最大の問題の 1 つは、それらを再現するのが非常に難しいことです。実証できずに失敗する可能性があることを予測する必要があります。

スレッドセーフでないライブラリ呼び出しをラップするのではなく、独自のコードを保護している場合、通常、ある場所に a を追加することで競合状態をトリガーできますSleep。投稿したコードでは、プロデューサーに対してそれを行う機会はありません (不変条件が壊れていても、内部で行われます) 。コンシューマーが 1 つしかない場合、空のキューをチェックするコンシューマーに関するque.push潜在的な TOCTTOU問題は存在しません。キューの実装に追加できればSleep、予測可能な方法で事態を悪化させることができます。

于 2013-01-28T13:23:01.720 に答える
-1

プロデューサーとコンシューマーが1つしかない場合は、キューコードがこのようなポップポーリングに対して安全である可能性があります。プロデューサープッシュが一時インデックス/ポインターを使用してデータを次の空のキュー位置に挿入し、インクリメントされた「一時インデックス」をキューの「次の空」メンバーにのみ格納する場合、queue.emptyは安全になるまでtrueを返す可能性があります。消費者によってポップされるデータ。このような操作は、設計されているか、偶然に発生する可能性があります。

複数のプロデューサーまたは複数のコンシューマーが存在する場合、遅かれ早かれ確実に爆発します。

編集-1つのプロデューサーと1つのコンシューマーでキューが安全であることが判明した場合でも、文書化されていない限り、キューに依存しないでください-一部のb * ** dは次のリリースで実装を変更します:(

于 2013-01-28T16:49:25.327 に答える