2

これは非常に面白いです...なぜこれが起こっているのかを正確に理解したいと思います。

public int numCounter;

private void button2_Click(object sender, EventArgs e)
{
 for (numCounter = 0; numCounter < 10; numCounter++)
 {
  Thread myThread = new Thread(myMethod);
  myThread.Start();
 }
}

public void myMethod()
{
 Console.WriteLine(numCounter);
}

ムーンフェイズによって結果は異なります... 3 3 4 4 5 6 7 8 9 10

または: 1 4 5 5 5 6 7 8 10 10

問題は...なぜこれが起こっているのですか?変数がインクリメントされた後にスレッドが開始された場合、なぜ更新されていない値を取る必要があるのですか????

4

4 に答える 4

2

結果をよく見てください。問題は、スレッドが更新されていない値を使用することではなく、値が何度も更新されたことです。

たとえば、2 番目の例では、イベントは次のようになっている可能性があります。

Thread 1   Thread 2     Thread 3
Start T2
i++
           WriteLine(1)
Start T3
i++
Start T4
i++
Start T5
i++
                         WriteLine(4)

スレッド化は、コンパイラと CPU の最適化、および CPU キャッシュのために、それよりもさらに複雑になる可能性があります。

于 2012-05-14T16:48:56.363 に答える
1

他の答えは正しいですが、なぜこれが起こるのかを詳しく説明したいと思います。「非決定論的である」と言うだけで正しいですが、十分に説明していません。

この動作は、書き込みプロセッサ (「メイン」スレッド) がメモリにフラッシュする前に最初にキャッシュ ラインに書き込むために発生します。他のスレッド (リーダー) は、ライターのキャッシュ ラインを覗くことができません。メモリへのフラッシュが発生した場合にのみ、データが伝播されます。

メモリへのフラッシュは、CPU ができる限りの速さで行われますが、更新が連続してすばやく行われると、一部が 1 つのストアにマージされます。そのため、番号が欠落しています - それらの書き込みはマージされています。

于 2012-05-14T18:42:42.700 に答える
0

スレッドが更新された値で開始したからといって、値が読み取られるコードに到達するまで値が同じままであるとは限りません。システムで何が起こっているかによって、Console.WriteLine に到達する前に、ループ内でいくつかの反復が行われる可能性があります。

各スレッドに異なる値を持たせたい場合は、ループ内で整数を宣言し、それを numCounter に初期化するのが最善の方法です。次に、スレッドで ParameterizedThreadStart を使用し、それにローカル値を渡します。または、整数のボックス化/ボックス化解除を回避するために、デリゲートをインラインで宣言します (このオプションでもループにスコープされた変数を使用する必要があります)。

for (int i = 0; i < 10; i++){
    var tmp = i;
    new Thread(() => {
        Console.WriteLine(tmp);
    }).Start();
}

また、すべてのスレッドの動作が完了する前に終了しないように、非仮説的な設定でスレッドの状態を追跡することもできます。ThreadPool を使用したくない場合は、ループのサイズの WaitHandles の配列を宣言し、スレッド化されたコードの実行が終了した後に WaitArray[tmp].Set() を呼び出します。ループの後、WaitHandle.WaitAll(WaitArray) ステートメントを使用して、すべてが終了するまでブロックします。ただし、ThreadPool とそれに関連するコールバック メカニズムを使用する方がおそらく簡単で安全です。

于 2012-05-14T18:43:31.623 に答える
0

私が見る限り、これは完全に非決定論的です。10 個のゼロから 10 個までのすべての方法で値を取得し、その間のすべての増分バリエーションを得ることができます。

numCounterその理由は、変数への同期および/またはシリアル化されたアクセスがまったくないためです。スレッドは、スレッドが実行されているときにのみそれを読み取ります。これは、任意の数の環境条件に基づいて、いつでも発生する可能性があります。

繰り返しなしですべての数字を取得する簡単な方法は次のとおりです。

public int numCounter;

private void button2_Click(object sender, EventArgs e)
{
 for (numCounter = 0; numCounter < 10; numCounter++)
 {
  Thread myThread = new Thread(myMethod);
  myThread.Start(numCounter);
 }
}

public void myMethod(object numCounter)
{
 Console.WriteLine(numCounter);
}

ここでの主な欠点は、1) の署名を変更する必要があること、2)から anにmyMethod変換するときにボックス化が発生すること、3) 出力が予想される順序 0..10 であることを保証できないことです。numCounterintobject

delegateこれは、aとBeginInvoke/を使用する少し異なるバージョンですEndInvoke(スレッド化を行うのが好きな方法です)。上記の欠点 #2 を排除しますが、それでも #1 と #3 を維持します。

public int numCounter;

private delegate void MyMethodDelegate(int numCounter);

private void button2_Click(object sender, EventArgs e)
{
 for (numCounter = 0; numCounter < 10; numCounter++)
 {
  MyMethodDelegate myDelegate = new MyMethodDelegate(myMethod);
  myDelegate.BeginInvoke(numCounter, myMethodDone, myDelegate);
 }
}

public void myMethod(int numCounter)
{
 Console.WriteLine(numCounter);
}

public void myMethodDone(IAsyncResult result)
{
 MyMethodDelegate myDelegate = result.AsyncState as MyMethodDelegate;

 if (myDelegate != null)
 {
  myDelegate.EndInvoke(result);
 }
}
于 2012-05-14T16:49:22.090 に答える