Иллюстрация использования ключевого слова volatile в C #

88

Я хотел бы написать небольшую программу, которая наглядно иллюстрирует поведение volatileключевого слова. В идеале это должна быть программа, которая выполняет одновременный доступ к энергонезависимому статическому полю и из-за этого ведет себя некорректно.

Добавление ключевого слова volatile в ту же программу должно решить проблему.

Этого мне не удалось достичь. Даже несколько раз попробовав, включив оптимизацию и т. Д., Я всегда получаю правильное поведение без ключевого слова volatile.

У вас есть идеи по этой теме? Вы знаете, как смоделировать такую ​​проблему в простом демонстрационном приложении? Это зависит от железа?

Ромен Вердье
источник

Ответы:

102

Добился рабочего примера!

Основная идея взята из вики, но с некоторыми изменениями для C #. Статья в вики демонстрирует это для статического поля C ++, похоже, что C # всегда тщательно компилирует запросы к статическим полям ... и я привожу пример с нестатическим:

Если вы запустите этот пример в режиме Release и без отладчика (то есть с помощью Ctrl + F5), тогда строка while (test.foo != 255)будет оптимизирована до 'while (true)', и эта программа никогда не вернется. Но после добавления volatileключевого слова вы всегда получаете «ОК».

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
Протестировано на .NET 4.0, как x86, так и x64 - может подтвердить, что пример все еще применим. Благодарность! :)
Роман Старков
1
Это хорошо! Я никогда не знал, что JIT выполняет такую ​​оптимизацию, а volatile отключает ее.
usr
3
Чтобы быть явным, вы добавляете ключевое слово volatile в объявление foo, верно?
JoeCool
21

Да, это зависит от оборудования (вы вряд ли увидите проблему без нескольких процессоров), но это также зависит от реализации. Спецификации модели памяти в спецификации CLR допускают то, что реализация CLR Microsoft не всегда делает.

Крейг Стунц
источник
6

На самом деле это не вопрос неисправности, когда ключевое слово volatile не указано, более того, когда оно не указано, может произойти ошибка. Обычно вы знаете, когда это так, лучше, чем компилятор!

Самый простой способ подумать об этом - это то, что компилятор мог бы, если бы захотел, встроить определенные значения. Помечая значение как изменчивое, вы говорите себе и компилятору, что значение может действительно измениться (даже если компилятор так не считает). Это означает, что компилятор не должен вставлять значения в строку, сохранять кеш или считывать значение раньше (в попытке оптимизировать).

На самом деле это не то же самое ключевое слово, что и в C ++.

MSDN имеет краткое описание здесь . Вот, возможно, более подробный пост по темам волатильности, атомарности и взаимоблокировки.

Рэй Хейс
источник
4

Это сложно продемонстрировать на C #, поскольку код абстрагируется виртуальной машиной, поэтому в одной реализации этой машины он работает правильно без изменчивости, в то время как в другой может произойти сбой.

Однако в Википедии есть хороший пример того, как продемонстрировать это на языке C.

То же самое может произойти в C #, если JIT-компилятор решит, что значение переменной не может измениться в любом случае, и, таким образом, создает машинный код, который даже больше не проверяет его. Если теперь другой поток изменяет значение, ваш первый поток все еще может попасть в цикл.

Другой пример - ожидание занятости.

Опять же, это может произойти и с C #, но это сильно зависит от виртуальной машины и от JIT-компилятора (или интерпретатора, если у него нет JIT ... теоретически, я думаю, что MS всегда использует JIT-компилятор, а также Mono использует один; но вы можете отключить его вручную).

Mecki
источник
4

Вот мой вклад в коллективное понимание этого поведения ... Это немного, просто демонстрация (на основе демонстрации xkip), которая показывает поведение изменчивого стиха с энергонезависимым (то есть "нормальным") значением int, бок о бок -в той же программе ... это то, что я искал, когда нашел эту ветку.

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 ведет себя плохо.

Я пробовал и не смог решить эту задачу. (На самом деле я довольно быстро сдался ;-). Во всем, что я пробовал со статическими варами, они ведут себя «правильно» независимо от того, изменчивы они или нет ... и мне бы хотелось получить объяснение, ПОЧЕМУ это так, если это действительно так ... компилятор не кэширует значения статических переменных в регистрах (т.е. вместо этого кэширует ссылку на этот адрес кучи)?

Нет, это не новый вопрос ... это попытка вернуть сообщество к исходному вопросу.

Corlettk
источник
2

Я наткнулся на следующий текст Джо Альбахари, который мне очень помог.

Я взял пример из приведенного выше текста, который немного изменил, создав статическое изменчивое поле. Когда вы удалите 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
    }
}
Мартейн Б
источник