0

私はこの件について多くのことを知っていると思っていましたが、次のことが私を困惑させます。これを実行する特定の PC では、「_x++」が 3 つのアセンブリ命令 (メモリからレジスタに値を移動、レジスタをインクリメント、メモリに書き戻す) に変換されることがわかっています。では、一体なぜ、単一の CPU にロックされている場合、値をインクリメントするスレッドは、値がインクリメントされる正確な時間にプリエンプトされず、レジスタ内にあり、メモリに書き戻されないのでしょうか? 誰もこれに対する答えを知っていますか? これは完全に運が良かったと推測していますが、正確に証明することはできません。

VS の逆アセンブリ ウィンドウでブレーク ポイントを設定し、デバッガーでいくつかの手順を実行することで、正しい結果を消すことができましたがinc eax、それは証拠ではありません。しっかりしたものではありません。

[このコードは意図的にスレッドセーフではありません。これは製品コードではなく、教育目的に使用されます。テーマを深く掘り下げようとしています。もちろん、キャッシュ ラインが見えなくなると、多くの情報が得られますが、それでも 3 つの命令があり、1 つのミスもありません。これは奇妙なことです。これは 4 コア CPU、x64 です。]

{
    private static int NumberOfThreads = 2;
    private const long IncrementIterations = 1000000000;

    private static int _x;

    static void Main(string[] args)
    {
        Process.GetCurrentProcess().ProcessorAffinity = (IntPtr)1;

        Console.WriteLine("Hit Enter");
        Console.ReadLine();

        while (true)
        {
            var barrier = new Barrier(NumberOfThreads);

            _x = 0;

            var threads = new List<Thread>();

            for (int i = 0; i < NumberOfThreads; i++)
            {
                var thread = new Thread(
                    delegate()
                    {
                        barrier.SignalAndWait();

                        for (int j = 0; j < IncrementIterations; j++)
                        {
                            _x++;
                        }
                    });

                thread.Start();

                threads.Add(thread);
            }

            BlockUntilAllThreadsQuit(threads);

            Console.WriteLine(_x);

            Console.WriteLine("Actual increments: " + (IncrementIterations * NumberOfThreads));

            if (_x != (IncrementIterations * NumberOfThreads))
            {
                Console.WriteLine("Observed: " + _x);
                break;
            }
        }

        Console.ReadLine();
    }

    private static void BlockUntilAllThreadsQuit(IEnumerable<Thread> threadsToWaitFor)
    {
        foreach (var thread in threadsToWaitFor)
        {
            thread.Join();
        }
    }
}
4

1 に答える 1

3

クロージャに引き上げられる_x変数を使用するのではなく、_xフィールドを持つクラスを使用すると、プログラムがより明確になります。そうは言っても、特にロードが完了してからストアが開始するまでの間に、スレッドが増分を実行しているときにスレッドがプリエンプションされた場合にのみ、増分はアトミックに失敗します。それは非常に小さなウィンドウです。任意のイベントが発生し、その瞬間にタスクの切り替えがトリガーされる可能性はありますが、その可能性はほとんどありません。ループの実行に必要な時間は、通常のタイマーティックイベントが常にチャンスの魔法のウィンドウを逃すようなものになる可能性があることに注意してください。

物事をもう少し良くテストしたい場合は、ループに小さなコードを追加して、タイミングを少しランダム化することをお勧めします(たとえば、randThing1に初期化された真正な自動変数を使用して、次のようにします。

  if(randthing&0x20000000)
    randthing ^ = 0x20043251); //帽子から選んだ数; 原始多項式
                              // 方が良いだろう。
  そうしないと
    randthing << = 1;

非周期的に、ループのタイミングが反復ごとに少し変化する可能性があります。

于 2012-06-18T22:35:23.673 に答える