Действительно ли нам нужно ключевое слово VOLATILE в C#?

Вот код, который я пробовал на своей рабочей станции.

class Program
{
    public static volatile bool status = true;

    public static void Main()
    {
        Thread FirstStart = new Thread(threadrun);
        FirstStart.Start();

        Thread.Sleep(200);

        Thread thirdstart = new Thread(threadrun2);
        thirdstart.Start();

        Console.ReadLine();

    }

    static void threadrun()
    {
        while (status)
        {
            Console.WriteLine("Waiting..");
        }
    }

    static void threadrun2()
    {
        status = false;
        Console.WriteLine("the bool value is now made FALSE");
    }
}

Как видите, я запустил три потока в Main. Затем с помощью точек останова я отслеживал потоки. Моя первоначальная концепция заключалась в том, что все три потока будут запущены одновременно, но мой поток точек останова показал, что поток выполнения потока следует один за другим (как и формат вывода, т.е. выполнение потоков сверху вниз). Ребята, почему так происходит?

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


person deepak sahu    schedule 16.12.2011    source источник
comment
Как вы ожидаете, что это произойдет при использовании отладчика?   -  person JonH    schedule 16.12.2011
comment
Почему вы считаете, что потоки будут выполняться одновременно?   -  person Alan    schedule 16.12.2011
comment
Тогда почему я не получаю никакой разницы в выводе при их использовании и не использовании?   -  person deepak sahu    schedule 16.12.2011
comment
Конечно, они не работают одновременно.   -  person Al Kepp    schedule 16.12.2011
comment
blogs.msdn.com/b/ericlippert/archive/2011/06/16/   -  person Otiel    schedule 16.12.2011
comment
Спасибо, Отиэль, ссылка на блог была полезной.   -  person deepak sahu    schedule 17.12.2011
comment
Вы не можете доказать, что Volatile бесполезен на вашем ноутбуке / настольном компьютере, вам нужен действительно аппаратный стек и интенсивные операции с памятью / процессором. Нелегко чувствовать боль многопоточности в Visual Studio/Laptop/Debugger.   -  person codeSetter    schedule 17.12.2011
comment
Ты боишься, что тебя застрелят. Однажды вы гуляете по тихому пригородному району без бронежилета, и никто в вас не стреляет, и поэтому вы делаете вывод, что бронежилеты не нужны для защиты от пуль в зоне боевых действий?   -  person Eric Lippert    schedule 17.12.2011


Ответы (4)


Хорошо, я постараюсь объяснить очень длинную историю как можно короче:

Номер 1: пытаться проверить поведение потоков с помощью отладчика так же полезно, как многократно запускать многопоточную программу и делать вывод, что она работает нормально, потому что из 100 тестов ни один не прошел неудачно: НЕПРАВИЛЬНО! Потоки ведут себя совершенно недетерминированным (некоторые сказали бы случайным) образом, и вам нужны разные методы, чтобы убедиться, что такая программа будет работать правильно.

Номер 2: использование volatile станет очевидным, как только вы удалите его, а затем запустите свою программу в режиме отладки, а затем переключитесь в режим выпуска. Я думаю, вас ждет сюрприз... В режиме Release компилятор оптимизирует код (включая инструкции по изменению порядка и кэширование значений). Теперь, если ваши два потока выполняются на разных процессорных ядрах, тогда ядро, выполняющее поток, проверяющий значение status, будет кэшировать его значение вместо того, чтобы повторно проверять его. Другой поток установит его, но первый никогда не увидит изменения: взаимоблокировка! volatile предотвращает возникновение подобных ситуаций.

В некотором смысле, volatile является охраной на случай, если код на самом деле не запустится (и, скорее всего, не запустится) в многопоточном сценарии так, как вы думаете.

person Tudor    schedule 17.12.2011
comment
Это немного вводит в заблуждение по нескольким причинам. Проблема не столько в режиме выпуска, сколько в работе без подключенного отладчика. Неважно, работает ли он на разных ядрах, у многопоточности с разделением времени здесь точно такая же проблема, потому что это не забавная глобальная проблема упорядочения, а проблема значения в регистре, а не повторное чтение из кеша, поэтому это никогда не меняется. - person harold; 17.12.2011
comment
@harold: я позволю себе не согласиться с отладчиком. Дело не в подключении отладчика, а в компиляции с оптимизацией или без нее. Вы можете воспроизвести эту проблему даже без использования IDE. - person Tudor; 17.12.2011
comment
Ну, это было бы верно для C, но компилятор C# все равно не оптимизирует, несмотря на то, что может сказать флаг оптимизации. Во всем виноват компилятор JIT. - person harold; 17.12.2011
comment
@harold: я только что воспроизвел проблему компиляции с помощью csc /o. Без флага не бывает. Попробуй сам. - person Tudor; 17.12.2011
comment
Да уже есть, правда не с этим кодом, и там только отладчик имел значение. - person harold; 17.12.2011
comment
Кстати, какую версию CLR вы использовали? - person harold; 17.12.2011
comment
Я использовал 2, посмотрю, что произойдет, если я обновлюсь, но я надеюсь, что вы правы - person harold; 17.12.2011
comment
Спасибо всем, ребята... Я пришел к выводу, что выполнение потока полностью НЕДЕТЕРМИНИСТИЧЕСКОЕ и зависит исключительно от типа и скорости процессора, а также от используемой техники оптимизации кода. И Volatile Usability можно отметить на большой вычислительной машине, а не на маленьком отладчике. Еще раз всем спасибо Удачного кодирования. - person deepak sahu; 19.12.2011

Ваш метод мышления ошибочен.

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

Именно поэтому многопоточное программирование является «сложным». Он часто не поддается специальному тестированию или даже большинству модульных тестов. Единственный способ сделать это эффективно — понять весь стек вашего программного и аппаратного обеспечения и составить схему каждого возможного случая с помощью конечных автоматов.

Таким образом, многопоточное программирование — это не то, что вы видели, а то, что может произойти, каким бы невероятным оно ни было.

person Kennet Belenky    schedule 16.12.2011

Тот факт, что ваш простой код не работает с volatile, ничего не значит. Ваш код слишком прост и не имеет ничего общего с volatile. Вам нужно написать очень ресурсоемкий код, чтобы создать четко видимую ситуацию гонки памяти.

Кроме того, ключевое слово volatile может быть полезно на платформах, отличных от x86/x64, с другими моделями памяти. (Я имею в виду, например, Itanium.)

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

person Al Kepp    schedule 16.12.2011
comment
Спасибо всем, ребята... Я пришел к выводу, что выполнение потока полностью НЕДЕТЕРМИНИСТИЧЕСКОЕ и зависит исключительно от типа и скорости процессора, а также от используемой техники оптимизации кода. И Volatile Usability можно отметить на большой вычислительной машине, а не на маленьком отладчике. Еще раз всем спасибо Удачного кодирования. - person deepak sahu; 19.12.2011

Затем с помощью точек останова я отслеживал потоки. Моя первоначальная концепция заключалась в том, что все три потока будут запущены одновременно, но мой поток точек останова показал, что поток выполнения потока следует один за другим (как и формат вывода, т.е. выполнение потоков сверху вниз). Ребята, почему это происходит?

Отладчик временно приостанавливает потоки, чтобы упростить отладку.

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

Скорее всего, вызовы Console.WriteLine fixing маскируют проблему. Скорее всего, они неявно создают для вас необходимый барьер памяти. Вот действительно простой фрагмент кода, который демонстрирует, что на самом деле существует проблема, когда volatile не используется для объявления переменной stop.

Скомпилируйте следующий код с конфигурацией Release и запустите его вне отладчика.

class Program
{
    static bool stop = false;

    public static void Main(string[] args)
    {
        var t = new Thread(() =>
        {
            Console.WriteLine("thread begin");
            bool toggle = false;
            while (!stop)
            {
                toggle = !toggle;
            }
            Console.WriteLine("thread end");
        });
        t.Start();
        Thread.Sleep(1000);
        stop = true;
        Console.WriteLine("stop = true");
        Console.WriteLine("waiting...");
        t.Join();
    }
}
person Brian Gideon    schedule 23.04.2012