Ілюструє використання летючого ключового слова в C #


88

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

Додавання мінливого ключового слова в ту саму програму має вирішити проблему.

Це те, чого я не зумів досягти. Навіть намагаючись кілька разів, увімкнувши оптимізацію тощо, я завжди отримую правильну поведінку без ключового слова 'volatile'.

Чи маєте Ви уявлення про цю тему? Чи знаєте ви, як змоделювати таку проблему в простому демонстраційному додатку? Це залежить від апаратного забезпечення?

Відповіді:


102

Я досягнув робочого прикладу!

Основна ідея, отримана від 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");
    }
}

5
Протестовано на .NET 4.0 як x86, так і x64 - може підтвердити, що приклад все ще застосовний. Дякую! :)
Роман Старков

1
Це мило! Я ніколи не знав, що JIT робить такий тип оптимізації, і ця мінливість відключає його.
usr

3
Щоб бути явним, ви додаєте ключове слово "volatile" до оголошення foo, так?
JoeCool

21

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


6

Насправді справа не в помилці, яка трапляється, коли не вказано ключове слово 'volatile', більше, що помилка може статися, коли воно не вказане. Як правило, ви будете знати, коли це так, краще, ніж компілятор!

Найпростіший спосіб думати про це - це те, що компілятор міг би, якщо захотів, вкласти певні значення. Позначаючи значення як нестабільне, ви говорите собі та компілятору, що значення може насправді змінитися (навіть якщо компілятор не вважає цього). Це означає, що компілятор не повинен вбудовувати значення, зберігати кеш чи читати значення раніше (у спробі оптимізації).

Ця поведінка насправді не є таким ключовим словом, як у C ++.

MSDN має короткий опис тут . Ось, можливо, більш поглиблений допис на теми Волатильність, Атомність та Блокування


4

Це важко продемонструвати на C #, оскільки код абстрагується віртуальною машиною, отже, на одній реалізації цієї машини він працює правильно без мінливості, тоді як на іншій він може вийти з ладу.

У Вікіпедії є хороший приклад того, як продемонструвати це на мові C.

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

Інший приклад - зайняте очікування.

Знову ж, це може трапитися і з C #, але це сильно залежить від віртуальної машини та компілятора JIT (або інтерпретатора, якщо у нього немає JIT ... теоретично, я думаю, що MS завжди використовує компілятор JIT, а також Mono один, але ви можете відключити його вручну).


4

Ось мій внесок у колективне розуміння цієї поведінки ... Це не багато, просто демонстрація (заснована на демонстрації xkip), яка показує поведінку летких віршів нелетучим (тобто "нормальним") значенням int, поруч -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 в регістрах (тобто замість цього кешує посилання на цю адресу кучі)?

Ні, це не нове питання ... це спроба повернути спільноту до вихідного питання.


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
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.