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

StackOverflow https://stackoverflow.com/questions/133270

  •  02-07-2019
  •  | 
  •  

Вопрос

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

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

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

У вас есть какие-нибудь идеи по этой теме?Знаете ли вы, как смоделировать такую проблему в простом демонстрационном приложении?Зависит ли это от аппаратного обеспечения?

Это было полезно?

Решение

У меня получился рабочий пример!

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

Если вы запустите этот пример в Освободить режим и без отладчика (т.е.используя 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");
    }
}

Другие советы

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

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

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

Это поведение на самом деле не является тем же ключевым словом, что и в C ++.

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

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

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

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

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

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

Вот мой вклад в коллективное понимание такого поведения...Это не так уж много, просто демонстрация (основанная на демо-версии xkip), которая показывает поведение volatile в качестве энергонезависимого (т. е."normal") значение 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: Мне было бы очень интересно ознакомиться с демонстрацией первоначального запроса, который был:"Я бы хотел посмотреть a статический изменчивый int правильно вести себя там, где a статический инт плохо себя ведет.

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

Нет, это не новый вопрос...это попытка управлять сообществом Назад к первоначальному вопросу.

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

Я взял пример из приведенного выше текста, который я немного изменил, создав статическое изменчивое поле.Когда вы удаляете 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
    }
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top