4

TLDR; 主な質問のバージョン:

  1. スレッドを操作している間、リストの内容を削除せず (順序を再編成)、新しいオブジェクトが完全に追加された後にのみ新しいオブジェクトを読み取る限り、1 つのスレッドでリストの内容を読み取り、別のスレッドでリストの内容を読み取ることは安全ですか?

  2. Int が 1 つのスレッドによって「古い値」から「新しい値」に更新されている間に、別のスレッドがこの Int を読み取ると、返される値が「古い値」でも「新しい値」でもないリスクがありますか?

  3. スレッドが重要な領域を「スキップ」することは可能ですか?

別のスレッドで実行されている 2 つのコードがあり、一方を他方のプロデューサーとして機能させたいと考えています。アクセスを待機している間、どちらのスレッドも「スリープ」したくありませんが、他のスレッドがこれにアクセスしている場合は、代わりに内部コードをスキップします。

私の当初の計画は、このアプローチを介してデータを共有することでした (そして、カウンターが十分に高くなったら、オーバーフローを避けるためにセカンダリ リストに切り替えます)。


私が最初に意図したフローの疑似コード。

Producer
{
Int counterProducer;
bufferedObject newlyProducedObject;
List <buffered_Object> objectsProducer;
    while(true) 
    {
        <Do stuff until a new product is created and added to newlyProducedObject>;
        objectsProducer.add(newlyProducedObject_Object);
        counterProducer++
    }
}


Consumer
{
Int counterConsumer;
Producer objectProducer; (contains reference to Producer class)
List <buffered_Object> personalQueue
    while(true)
        <Do useful work, such as working on personal queue, and polish nails if no personal queue>
        //get all outstanding requests and move to personal queue
        while (counterConsumer < objectProducer.GetcounterProducer())
        {
            personalQueue.add(objectProducer.GetItem(counterconsumer+1));
            counterConsumer++;
        }
}

これを見ると、一見問題ないように見えましたが、キューから半分構築された製品を取得することはないことがわかっていたので、リストがどこにあるかに関係なく、途中でスレッドの切り替えが発生したとしても、リストのステータスは問題にならないはずです。プロデューサーは新しいオブジェクトを追加しています。この仮定は正しいですか、それともここに問題がある可能性がありますか? (私の推測では、消費者がリスト内の特定の場所を要求し、新しいオブジェクトが最後に追加され、オブジェクトが削除されることはなく、これが問題にならないためです)

しかし、私の目を引いたのは、「counterProducer++」である間に「counterProducer」が不明な値であるという同様の問題が発生する可能性があることでした。これにより、一時的な値が「null」または不明な値になる可能性がありますか? これは潜在的な問題になりますか?

私の目標は、ミューテックスを待っている間、2 つのスレッドのどちらもロックせずにループを続行することです。これが、ロックがないため、最初に上記を作成した理由です。

リストの使用が問題を引き起こす場合、私の回避策は、リンクされたリストの実装を作成し、2 つのクラス間で共有し、カウンターを使用して新しい作業が追加されたかどうかを確認し、personalQueue が新しく移動する間、最後の場所を保持することです。パーソナル キューに追加します。したがって、プロデューサーは新しいリンクを追加し、コンシューマーはそれらを読み取り、以前のリンクを削除します。(リストにカウンターはなく、追加および削除された量を知るための外部カウンターのみ)


counterConsumer++ リスクを回避するための代替疑似コード (これについては助けが必要です)。

Producer
{
Int publicCounterProducer;
Int privateCounterProducer;
bufferedObject newlyProducedObject;
List <buffered_Object> objectsProducer;
    while(true) 
    {
        <Do stuff until a new product is created and added to newlyProducedObject>;
        objectsProducer.add(newlyProducedObject_Object);
        privateCounterProducer++
        <Need Help: Some code that updates the publicCounterProducer to the privateCounterProducer if that variable is not 

locked, else skips ahead, and the counter will get updated at next pass, at some point the consumer must be done reading stuff, and 

new stuff is prepared already>      
    }
}


Consumer
{
Int counterConsumer;
Producer objectProducer; (contains reference to Producer class)
List <buffered_Object> personalQueue
    while(true)
        <Do useful work, such as working on personal queue, and polish nails if no personal queue>
        //get all outstanding requests and move to personal queue
        <Need Help: tries to read the publicProducerCounter and set readProducerCounter to this, else skips this code>
        while (counterConsumer < readProducerCounter)
        {
            personalQueue.add(objectProducer.GetItem(counterconsumer+1));
            counterConsumer++;
        }
}

したがって、コードの 2 番目の部分の目標であり、これをコーディングする方法を理解できませんでしたが、もう一方が publicCounterProducer を更新する「重要な領域」にある場合に、両方のクラスが他方を待たないようにすることです。ロック機能を正しく読み取ると、スレッドは解放を待ってスリープ状態になりますが、これは私が望んでいるものではありません。ただし、それを使用しなければならなくなる可能性があります。その場合、最初の疑似コードでそれを行い、値の取得時に「ロック」を設定するだけです。

私の多くの質問で私を助けてくれることを願っています。

4

2 に答える 2

5
  1. いいえ、安全ではありません。オブジェクトを追加した.Add後、内部データ構造を更新する前に、コンテキストの切り替えが発生する可能性があります。ListList

  2. である場合int32、またはそうでありint64、x64 プロセスで実行している場合、リスクはありません。ただし、疑問がある場合は、Interlockedクラスを使用してください。

  3. はい、Semaphoreを使用できます。重要な領域に入る時間になったら、タイムアウトを取るWaitOneオーバーロードを使用します。タイムアウト 0 を渡しWaitOneます。 true が返された場合は、ロックの取得に成功し、入ることができます。false が返された場合は、ロックを取得していないため、入力しないでください。

System.Collections.Concurrent名前空間を実際に確認する必要があります。特に、 を見てくださいBlockingCollectionTry*ブロックせずにコレクションにアイテムを追加/削除するために使用できる一連の演算子があります。

于 2013-04-01T09:44:41.617 に答える
4

スレッドを操作している間、リストの内容を削除せず (順序を再編成)、新しいオブジェクトが完全に追加された後にのみ新しいオブジェクトを読み取る限り、1 つのスレッドでリストの内容を読み取り、別のスレッドでリストの内容を読み取ることは安全ですか?

いいえ、そうではありません。リストに項目を追加することの副作用として、その下にある配列が再割り当てされることがあります。の現在の実装はList<T>、古いデータを内部参照にコピーする前に内部参照を更新するため、複数のスレッドが正しいサイズのリストを観察する可能性がありますが、データは含まれていません。

Int が 1 つのスレッドによって「古い値」から「新しい値」に更新されている間に、別のスレッドがこの Int を読み取ると、返される値が「古い値」でも「新しい値」でもないリスクがありますか?

いいえ、int の更新はアトミックです。しかし、2 つのスレッドが両方とも同時にインクリメントcounterProducerしていると、うまくいきません。を使用Interlocked.Increment()してインクリメントする必要があります。

スレッドが重要な領域を「スキップ」することは可能ですか?

いいえ。ただし、(たとえば)WaitHandle.WaitOne(int)を使用して、待機が成功したかどうかを確認し、それに応じて分岐することができます。WaitHandleなどのいくつかの同期クラスによって実装されManualResetEventます。

ちなみに、組み込みの Producer/Consumer クラスを使用していない理由はありますBlockingCollection<T>か? BlockingCollection は (ドキュメントを読んだ後で) 使いやすく、代わりに使用することをお勧めします。

于 2013-04-01T09:43:53.927 に答える