Нужно ли, чтобы это поле было изменчивым?

У меня есть поток, который вращается до тех пор, пока значение int, измененное другим потоком, не станет определенным значением.

int cur = this.m_cur;
while (cur > this.Max)
{
    // spin until cur is <= max
    cur = this.m_cur; 
}

Должен ли this.m_cur быть объявлен volatile, чтобы это работало? Возможно ли, что это будет вращаться вечно из-за оптимизации компилятора?

3 ответа

Решение

Да, это жесткое требование. Компилятору Just-in-Time разрешено сохранять значение m_cur в регистре процессора, не обновляя его из памяти. Джиттер x86 на самом деле делает, джиттер x64 нет (по крайней мере, в последний раз, когда я смотрел на него).

Ключевое слово volatile требуется для подавления этой оптимизации.

Volatile означает нечто совершенно иное на ядрах Itanium, процессор со слабой моделью памяти. К сожалению, именно это вошло в библиотеку MSDN и Спецификацию языка C#. Что это будет означать для ядра ARM, еще неизвестно.

У блога ниже есть некоторые захватывающие детали о модели памяти в C#. Короче говоря, кажется безопаснее использовать ключевое слово volatile.

http://igoro.com/archive/volatile-keyword-in-c-memory-model-explained/

Из блога ниже

class Test
{
    private bool _loop = true;

    public static void Main()
    {
        Test test1 = new Test();

        // Set _loop to false on another thread
        new Thread(() => { test1._loop = false;}).Start();

        // Poll the _loop field until it is set to false
        while (test1._loop == true) ;

        // The loop above will never terminate!
    }
}

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

Это зависит от того, как изменяется m_cur. Если он использует нормальный оператор присваивания, такой как m_cur--;тогда оно должно быть изменчивым. Однако, если он изменяется с помощью одной из операций Interlocked, то этого не происходит, потому что методы Interlocked автоматически вставляют барьер памяти, чтобы гарантировать, что все потоки получат памятку.

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

Тем не менее, как и другие говорили, избирательные петли чрезвычайно расточительны. Было бы лучше приостановить поток, который должен ждать, и позволить тому, кто модифицирует m_cur, взять на себя ответственность за его пробуждение, когда придет время. И Monitor.Wait(), и Monitor.Pulse() и AutoResetEvent могут хорошо подходить для этой задачи, в зависимости от ваших конкретных потребностей.

Другие вопросы по тегам