2017-11-15 5 views
2

私はマルチスレッド環境でvolatileの使用を理解しようとしています。インターネット上のanother source of knowledgeから次のコードで:フラグフィールドにvolatileキーワードを使用する

class Program 
{ 
    static string _result; 
    //static volatile bool _done; 
    static bool _done; 

    static void SetVolatile() 
    { 
     // Set the string. 
     _result = "Dot Net Perls"; 
     // The volatile field must be set at the end of this method. 
     _done = true; 
    } 

    static void Main() 
    { 
     // Run the above method on a new thread. 
     new Thread(new ThreadStart(SetVolatile)).Start(); 

     // Wait a while. 
     Thread.Sleep(200); 

     // Read the volatile field. 
     if (_done) 
     { 
      Console.WriteLine(_result); 
     } 
    } 
} 

揮発性キーワードの実証された使用は、キャッシュに格納された値を読み取ることからスレッドを防ぐべきです。この代わりに、実際の値をチェックする必要があります。

したがって、揮発性のない_doneはまだ偽の値(キャッシュからの読み取り)を持ち、Console.WriteLineステートメントは実行しないでください。

残念ながら、このコードをデバッグ/リリースモードでvolatileキーワードなしで実行すると、常に出力が生成されます。この特定の例のポイントは何ですか?

+0

コンパイラ(静的およびJIT)*は、標準を厳密に読めば、割り当てが決して見られないように読み込みを最適化することができます。しかし、これは必須ではなく、実際には.NET用(過去または現在)のJITコンパイラが実際にこれを行うとは思っていません。少なくともこの特定のケースではなく、x86/x64ではありません。 –

+0

このようなことを理解したい場合は、いくつかの[重大なもの](http://www.albahari.com/threading/part4.aspx#_The_volatile_keyword)を読むことをお勧めしますか? – Sinatr

+0

*「この例はvolatile修飾子なしで正しく機能するという点で理想的ではないことに注意してください。実際の例を提供するのではなく、 [インターネット](https://www.dotnetperls.com/volatile)から。 – Sinatr

答えて

3

すでに述べたように、volatileキーワードを使用していないとしても、すべての状況ですべての読み取りがキャッシュされる必要はありません。それらはキャッシュされているか、キャッシュされていない可能性があります。あなたはより多くの再現可能例をしたい場合でも、これを試してみてください。

class Program { 
    static string _result; 
    //static volatile bool _done; 
    static bool _done; 
    static void SetVolatile() { 
     // Set the string. 
     _result = "Dot Net Perls"; 
     // The volatile field must be set at the end of this method. 
     _done = true; 
    } 

    static void Main() { 
     // Run the above method on a new thread. 
     new Thread(new ThreadStart(SetVolatile)).Start(); 

     // prevent compiler to throw away empty while loop 
     // by doing something in it 
     int i = 0; 
     while (!_done) { 
      i++; 
     } 
     Console.WriteLine("done " + i); 
    } 
} 

は、ここでは繰り返しwhileループで _doneを読んで、それがキャッシュされる可能性が高くなります。プログラムは「完了」メッセージで終了する必要がありますが、他のスレッドからの _doneへの変更が気付かれないため、終了しません。

0

それはECMA-335を読み取ることが良いでしょう、§I.12.6
主なポイントは以下のとおりです。最適化することができ

  • プログラム(ロット)
  • ネイティブコードにCILを変換し、最適化コンパイラは、揮発性操作,を除去してはならないし、複数の揮発性操作を1つの操作に統合してはならない。


この場合、コードに最適化することができます。それが正常に動作します(最適化なし)デバッグモードで

private bool flag = true; 
public void LoopReadHoistingTest() 
{ 
    Task.Run(() => { flag = false; }); 
    while (flag) 
    { 
     // Do nothing 
    } 
} 


は、次のコードを試してみてください。 リリースモード(最適化あり)では、読み取りをループ外に移動することは非常に一般的な最適化であるため、永遠にハングします。
フィールドvolatileを指定した場合(またはVolatile.Readメソッド、またはInterlockedメソッドの一部を使用する場合)、その場合は最適化が禁止されているため動作します。

例では(ループなし)、Thread.Sleepは暗黙的なメモリバリアを行います(これは禁止されておらず、驚きの少ないコードで動作するためです)ので、メモリから値を読み取ります。しかし、暗黙的なメモリバリアを行わなければならないという仕様はありません。実装によっては、そうでないかもしれません(または、仕様で見つける必要があります)。

関連する問題