Иллюстрирует использование ключевого слова volatile в C#
Я хотел бы закодировать небольшую программу, которая визуально иллюстрирует поведение volatile ключевое слово. В идеале, это должна быть программа, которая выполняет параллельный доступ к энергонезависимому статическому полю и которая получает неправильное поведение из-за этого.
добавление ключевого слова volatile в ту же программу должно устранить проблему.
вот чего мне не удалось добиться. Даже попробовал несколько раз, включив оптимизацию и т. д. Я всегда получаю правильный поведение без ключевого слова volatile.
есть ли у вас какие-либо идеи по этой теме? Вы знаете, как имитировать такую проблему в простом демо-приложении? Это зависит от оборудования?
6 ответов:
я добился рабочего примера!
основная идея получена из 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"); } }
Да, это зависит от оборудования (вы вряд ли увидите проблему без нескольких процессоров), но это также зависит от реализации. Спецификации модели памяти в спецификации среды CLR позволяют делать то, что не обязательно делает реализация среды CLR Microsoft. Лучшая документация, которую я видел по ключевому слову volatile, -это сообщение в блоге Джо Даффи. Обратите внимание, что он говорит, что документация MSDN "очень вводит в заблуждение."
на самом деле это не вопрос ошибки, когда ключевое слово "volatile" не указано, более того, ошибка может произойти, когда она не была указана. Как правило, вы будете знать, когда это происходит лучше, чем компилятор!
самый простой способ думать об этом-это то, что компилятор мог бы, если бы захотел, встроить определенные значения. Отмечая значение как изменчивое, вы говорите себе и компилятору, что значение может фактически измениться (даже если компилятор так не считает). Это означает, что компилятор не должен вставлять значения в строку, хранить кэш или читать значение на ранней стадии (в попытке оптимизировать).
Это поведение на самом деле не то же самое ключевое слово, что и в C++.
MSDN имеет краткое описание здесь. Вот, пожалуй, более глубокий пост на темы летучесть, атомарность и блокировка
Это трудно продемонстрировать в C#, так как код абстрагируется виртуальной машиной, поэтому на одной реализации этой машины он работает правильно без volatile, в то время как он может потерпеть неудачу на другом.
Википедия имеет хороший пример, как продемонстрировать это в C, хотя.
то же самое может произойти в C#, если JIT-компилятор решит, что значение переменной не может измениться в любом случае и, таким образом, создает машинный код, который даже не проверяет его длиннее. Если теперь другой поток изменял значение, ваш первый поток все еще может быть пойман в цикле.
другой пример занят ожиданием.
опять же, это может произойти и с C#, но это сильно зависит от виртуальной машины и от JIT-компилятора (или интерпретатора, если у него нет JIT... теоретически, я думаю, что MS всегда использует JIT-компилятор, а также Mono использует его; но вы можете отключить его вручную).
вот мой вклад в коллективное понимание этого поведения... Это не так много, просто демонстрация (основанная на демо 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: Я был бы очень заинтересован в демонстрации оригинального запроса, который был: "я хотел бы видеть a статический volatile int ведет себя правильно где a статический int плохо себя ведет.
Я пытался и потерпел неудачу в этом вызове. (На самом деле я сдался довольно быстро ;-). Во всем, что я пробовал со статическими vars они ведут себя" правильно " независимо от того, являются ли они летучие ... и я хотел бы объяснить, почему это так, если это действительно так... Это то, что компилятор не кэширует значения статических vars в регистрах (т. е. он кэширует 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 } }
Comments