web-dev-qa-db-ja.com

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

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

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

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

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

87
Romain Verdier

実例を達成しました!

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

この例をReleaseモードおよびデバッガなしで(つまり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");
    }
}
101
xkip

はい、それはハードウェアに依存します(複数のプロセッサがないと問題が発生することはほとんどありません)が、実装にも依存します。 CLR仕様のメモリモデル仕様では、CLRのMicrosoft実装が必ずしも行う必要のないことを許可しています。 volatileキーワードについて私が見た中で最も優れたドキュメントは このブログのJoe Duffyによるブログ投稿 です。 MSDNのドキュメントは「非常に誤解を招く」と彼は言っていることに注意してください。

21
Craig Stuntz

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

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

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

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

6
Ray Hayes

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

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:元のリクエストのデモに非常に興味があります。それは、「見たいa static = volatile intが正しく動作する場所a static intが正しく動作しません。

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

いいえ、これは新しい質問ではありません...コミュニティを元の質問に戻します

4
corlettk

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

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

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

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

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

4
Mecki

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

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

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
    }
}
2
Martijn B