C#でのvolatileキーワードの使用例


88

volatileキーワードの動作を視覚的に示す小さなプログラムをコーディングしたいと思います。理想的には、非揮発性の静的フィールドへの同時アクセスを実行し、そのために正しくない動作をするプログラムである必要があります。

同じプログラムにvolatileキーワードを追加すると、問題が修正されます。

私がなんとか成し遂げられなかったもの。何度か試したり、最適化を有効にしたりしても、「volatile」キーワードなしで常に正しい動作が得られます。

このトピックについて何か知っていますか?簡単なデモアプリでそのような問題をシミュレートする方法を知っていますか?ハードウェアに依存しますか?

回答:


102

実用例を達成しました!

主なアイデアはwikiから受け取りましたが、C#にいくつかの変更が加えられています。Wikiの記事では、C ++の静的フィールドについてこれを示しています。C#は常に静的フィールドへのリクエストを慎重にコンパイルしているようです...

この例をリリースモードでデバッガなしで(つまり、Ctrl + F5を使用して)実行while (test.foo != 255)すると、行は 'while(true)'に最適化され、このプログラムは戻りません。しかし、volatileキーワードを追加した後は、常に「OK」になります。

class Test
{
    /*volatile*/ int foo;

    static void Main()
    {
        var test = new Test();

        new Thread(delegate() { Thread.Sleep(500); test.foo = 255; }).Start();

        while (test.foo != 255) ;
        Console.WriteLine("OK");
    }
}

5
x86とx64の両方の.NET 4.0でテスト-サンプルが引き続き適用可能であることを確認できます。ありがとう!:)
ローマン・スターコフ2011年

1
いいね!JITがこの種の最適化を行い、volatileが無効にすることを知りませんでした。
usr

3
明確に言うと、fooの宣言に「volatile」キーワードを追加しているのですね。
JoeCool

21

はい、それはハードウェアに依存します(複数のプロセッサがないと問題が発生することはほとんどありません)が、実装にも依存します。CLR仕様のメモリモデル仕様では、CLRのMicrosoft実装が必ずしも行う必要のないことを許可しています。


6

「volatile」キーワードが指定されていない場合に発生するのは、実際には障害の問題ではなく、指定されていない場合にエラーが発生する可能性があります。一般的には、これがコンパイラーよりも優れている場合を知るでしょう。

それについて考える最も簡単な方法は、コンパイラが必要に応じて特定の値をインライン化できることです。値を揮発性としてマークすることで、自分自身とコンパイラーに、値が実際に変更される可能性があることを伝えます(コンパイラーがそうではないとしても)。これは、コンパイラーがインライン値を使用したり、キャッシュを保持したり、値を早期に(最適化のために)読み取ったりしてはならないことを意味します。

この動作は、C ++の場合と実際には同じキーワードではありません。

MSDNはここに短い説明があります。おそらく、ボラティリティ、原子性、およびインターロッキングのテーマに関する、より詳細な投稿です。


4

コードは仮想マシンによって抽象化されているため、C#でデモンストレーションするのは難しいため、このマシンの1つの実装では、揮発性がなくても正しく動作しますが、別のマシンでは失敗する可能性があります。

ウィキペディアには、Cでそれをデモンストレーションする方法の良い例があります。

JITコンパイラが変数の値を変更できないと判断し、それ以上チェックしないマシンコードを作成すると、C#でも同じことが起こります。別のスレッドが値を変更していた場合、最初のスレッドがまだループに捕捉されている可能性があります。

別の例は、ビジー待機です。

繰り返しますが、これはC#でも発生する可能性がありますが、仮想マシンとJITコンパイラー(またはJITがない場合はインタープリター)に強く依存しています...理論的には、MSは常にJITコンパイラーを使用し、Monoも使用すると思います1つですが、手動で無効にできる場合があります)。


4

これは、この動作の集合的な理解への私の貢献です...それはそれほどではありません。 -側、同じプログラムで...これは私がこのスレッドを見つけたときに探していたものです。

using System;
using System.Threading;

namespace VolatileTest
{
  class VolatileTest 
  {
    private volatile int _volatileInt;
    public void Run() {
      new Thread(delegate() { Thread.Sleep(500); _volatileInt = 1; }).Start();
      while ( _volatileInt != 1 ) 
        ; // Do nothing
      Console.WriteLine("_volatileInt="+_volatileInt);
    }
  }

  class NormalTest 
  {
    private int _normalInt;
    public void Run() {
      new Thread(delegate() { Thread.Sleep(500); _normalInt = 1; }).Start();
      // NOTE: Program hangs here in Release mode only (not Debug mode).
      // See: http://stackoverflow.com/questions/133270/illustrating-usage-of-the-volatile-keyword-in-c-sharp
      // for an explanation of why. The short answer is because the
      // compiler optimisation caches _normalInt on a register, so
      // it never re-reads the value of the _normalInt variable, so
      // it never sees the modified value. Ergo: while ( true )!!!!
      while ( _normalInt != 1 ) 
        ; // Do nothing
      Console.WriteLine("_normalInt="+_normalInt);
    }
  }

  class Program
  {
    static void Main() {
#if DEBUG
      Console.WriteLine("You must run this program in Release mode to reproduce the problem!");
#endif
      new VolatileTest().Run();
      Console.WriteLine("This program will now hang!");
      new NormalTest().Run();
    }

  }
}

上記のいくつかの本当に優れた簡潔な説明といくつかの素晴らしい参考文献があります。私の頭をvolatile動かすのを手伝ってくれたすべてに感謝します(volatile私の最初の本能がどこだったかに依存しないことを知るのに少なくとも十分lockです)。

乾杯、そしてすべての魚に感謝します。キース。


PS:「私が見てみたい:私は非常にした元の要求のデモに興味があると思い静的揮発性のint正しく動作静的の int型の誤動作だけを。

私はこの挑戦を試み、失敗しました。(実際、私はかなり早くあきらめました;-)。私が静的変数で試したすべてにおいて、それらは揮発性であるかどうかに関係なく「正しく」動作します...そして実際にそうである場合、私はなぜそうであるかについての説明が欲しいです...コンパイラーは静的変数のをレジスターにキャッシュしません(つまり、代わりそのヒープ・アドレス参照をキャッシュします)?

ありませんこれは新しい問題ではない...それはコミュニティStear氏しようとする試みだ背中を元の質問に。


2

次のJoe Albahariのテキストに出会いました。

静的な揮発性フィールドを作成することにより、上記のテキストから少し変更した例を取得しました。volatileキーワードを削除すると、プログラムは無期限にブロックされます。この例をリリースモードで実行します。

class Program
{
    public static volatile bool complete = false;

    private static void Main()
    {           
        var t = new Thread(() =>
        {
            bool toggle = false;
            while (!complete) toggle = !toggle;
        });

        t.Start();
        Thread.Sleep(1000); //let the other thread spin up
        complete = true;
        t.Join(); // Blocks indefinitely when you remove volatile
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.