[c#] C #에서 volatile 키워드의 사용법 설명

volatile키워드 의 동작을 시각적으로 보여주는 작은 프로그램을 코딩하고 싶습니다 . 이상적으로는 비 휘발성 정적 필드에 대한 동시 액세스를 수행하고 그로 인해 잘못된 동작을하는 프로그램이어야합니다.

같은 프로그램에 volatile 키워드를 추가하면 문제가 해결됩니다.

내가 달성하지 못한 것. 여러 번 시도하여 최적화 등을 활성화해도 ‘volatile’키워드없이 항상 올바른 동작을 얻습니다.

이 주제에 대해 알고 있습니까? 간단한 데모 앱에서 이러한 문제를 시뮬레이션하는 방법을 알고 있습니까? 하드웨어에 의존합니까?



답변

나는 실제적인 예를 얻었습니다!

위키에서받은 주요 아이디어이지만 C #에 대한 일부 변경 사항이 있습니다. 위키 기사는 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");
    }
}


답변

예, 하드웨어에 따라 다르지만 (여러 프로세서 없이는 문제가 발생하지 않을 수 있음) 구현에 따라 다릅니다. CLR 사양의 메모리 모델 사양은 CLR의 Microsoft 구현이 반드시 수행 할 필요는없는 작업을 허용합니다.


답변

‘volatile’키워드가 지정되지 않았을 때 발생하는 오류가 아니라 지정되지 않은 경우 오류가 발생할 수 있습니다. 일반적으로 이것이 컴파일러보다 더 나은 경우를 알게 될 것입니다!

그것에 대해 생각하는 가장 쉬운 방법은 컴파일러가 원하는 경우 특정 값을 인라인 할 수 있다는 것입니다. 값을 휘발성으로 표시함으로써 자신과 컴파일러에게 값이 실제로 변경 될 수 있음을 알리는 것입니다 (컴파일러가 그렇게 생각하지 않더라도). 즉, 컴파일러가 값을 인라인하거나 캐시를 유지하거나 값을 일찍 읽어서는 안됩니다 (최적화를 시도하기 위해).

이 동작은 실제로 C ++에서와 동일한 키워드가 아닙니다.

MSDN에는 여기에 간단한 설명이 있습니다 . 다음은 변동성, 원 자성 및 연동 에 대한보다 심층적 인 게시물입니다.


답변

코드가 가상 머신에 의해 추상화 되었기 때문에 C #으로 설명하기가 어렵습니다. 따라서이 머신의 한 구현에서는 휘발성없이 올바르게 작동하지만 다른 머신에서는 실패 할 수 있습니다.

위키 백과에는 C로 시연하는 방법에 대한 좋은 예가 있습니다.

JIT 컴파일러가 변수의 값이 어쨌든 변경 될 수 없다고 판단하여 더 이상 확인하지 않는 기계어 코드를 생성하면 C #에서도 동일한 일이 발생할 수 있습니다. 이제 다른 스레드가 값을 변경하는 경우 첫 번째 스레드가 여전히 루프에 걸릴 수 있습니다.

또 다른 예는 Busy Waiting입니다.

다시 말하지만, 이것은 C #에서도 발생할 수 있지만 가상 머신과 JIT 컴파일러 (또는 JIT가없는 경우 인터프리터 … 이론 상으로는 MS가 항상 JIT 컴파일러를 사용하고 Mono도 사용한다고 생각합니다. 하지만 수동으로 비활성화 할 수 있습니다.)


답변

이 동작에 대한 집단적 이해에 대한 저의 공헌은 다음과 같습니다. 휘발성 구절의 동작을 비 휘발성 (즉, “정상”) int 값과 나란히 표시하는 데모 (xkip 데모 기반)에 불과합니다. -side, 같은 프로그램에서 …이 스레드를 발견했을 때 찾던 것입니다.

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의 무례한 행동을.

나는이 도전을 시도했지만 실패했습니다. (사실 나는 꽤 빨리 포기했다 ;-). 내가 정적 vars로 시도한 모든 것에서 휘발성 여부에 관계없이 “올바르게”동작합니다 . 그리고 실제로 그럴 경우 왜 그런지 설명하고 싶습니다. 컴파일러는 레지스터의 정적 변수 을 캐시하지 않습니다 (즉, 대신 해당 힙 주소에 대한 참조를 캐시 함 )?

아니 이것은 지역 사회 stear하기위한 시도이다 … 새로운 질문하지 않습니다 다시 원래의 질문을.


답변

나는 많은 도움을 준 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
    }
}


답변