7

リヒターの本でこのコードを見ました:

次のコードは、スレッド プール スレッドがメソッドをすぐに呼び出し、その後 2 秒ごとに呼び出す方法を示しています。

/*1*/    internal static class TimerDemo
/*2*/    {
/*3*/        private static Timer s_timer;
/*4*/        public static void Main()
/*5*/        {
/*6*/            Console.WriteLine("Checking status every 2 seconds");
/*7*/            // Create the Timer ensuring that it never fires. This ensures that
/*8*/            // s_timer refers to it BEFORE Status is invoked by a thread pool thread
/*9*/            s_timer = new Timer(Status, null, Timeout.Infinite, Timeout.Infinite);
/*10*/            // Now that s_timer is assigned to, we can let the timer fire knowing
/*11*/            // that calling Change in Status will not throw a NullReferenceException
/*12*/            s_timer.Change(0, Timeout.Infinite);
/*13*/            Console.ReadLine(); // Prevent the process from terminating
/*14*/        }
/*15*/        // This method's signature must match the TimerCallback delegate
/*16*/        private static void Status(Object state)
/*17*/        {
/*18*/            // This method is executed by a thread pool thread
/*20*/            Console.WriteLine("In Status at {0}", DateTime.Now);
/*21*/            Thread.Sleep(1000); // Simulates other work (1 second)
/*22*/            // Just before returning, have the Timer fire again in 2 seconds
/*23*/            s_timer.Change(2000, Timeout.Infinite);
/*24*/            // When this method returns, the thread goes back
/*25*/            // to the pool and waits for another work item
/*26*/        }
/*27*/    }

しかし、(すみません)まだ線の#7,#8意味がわかりません

そしてもちろん-なぜ初期化されたのですか(行#9)Timeout.Infinite(これは明らかに:「タイマーを開始しないでください」)

(オーバーラップを防ぐための一般的な目的は理解していますが、ここにはGC競合状態 povもあると思います。)

編集

名前空間はSystem.Threading

4

2 に答える 2

11

GC とは関係ないと思いますが、競合状態を避けるためです:

代入操作はアトミックではありません。まず Timer オブジェクトを作成してから代入します。

したがって、ここにシナリオがあります:

  • new Timer(...)タイマーを作成し、「カウント」を開始します

  • 現在のスレッドは、割り当てが終了する前に横取りされます=>s_timerまだ null です

  • タイマーは別のスレッドでウェイクアップして呼び出しますStatusが、最初のスレッドはまだ割り当て操作を完了していません!

  • Statusnull 参照s_timerであるアクセス=> BOOM!

彼の方法では、たとえば同じシナリオでは起こり得ません。

  • タイマーは作成されますが、開始されません

  • 現在のスレッドはプリエンプトされます

  • タイマーがまだイベントの発生を開始していないため、何も起こりません

  • 初期スレッドが再び実行されています

  • 割り当てを終了します=>s_timer タイマーを参照します

  • タイマーは安全に開始されます: は有効な参照であるStatusため、今後の呼び出しはすべて有効です。s_timer

于 2013-06-23T17:30:10.987 に答える
3

それはレースですが、目に見える以上のものがあります。明らかな障害モードは、メイン スレッドがプロセッサを失い、1 秒以上実行されない場合です。したがって、コールバックで s_timer 変数 kaboom を更新することはありません。

複数のプロセッサ コアを搭載したマシンでは、さらに微妙な問題が発生します。更新された変数値は、コールバック コードを実行する CPU コアで実際に表示される必要があります。キャッシュを介してメモリを読み取ると、そのキャッシュには古いコンテンツが含まれる可能性があり、読み取り時に s_time 変数が null のままになる可能性があります。これには通常、メモリ バリアが必要です。その低レベル バージョンは Thread.MemoryBarrier() メソッドから入手できます。投稿されたバージョンには、これが発生することを保証するコードはまったくありません。

メモリバリアは暗黙的であるため、実際には機能します。オペレーティング システムは、スレッドプール スレッドを開始することはできません。ここでは、コールバックを実行するために必要であり、それ自体がメモリ バリアを取得する必要があります。この副作用により、コールバック スレッドが s_time 変数の更新値を使用することも保証されます。この副作用に頼っても賞金は得られませんが、実際には機能します。しかし、Richter の回避策が使用されない場合も機能しません。割り当ての前にバリアが取得される可能性があるためです。したがって、Itanium や ARM などのメモリ モデルが弱いプロセッサでは、障害モードが発生する可能性が高くなります。

于 2013-06-23T18:45:56.750 に答える