Нужно ли, чтобы это поле было изменчивым?
У меня есть поток, который вращается до тех пор, пока значение 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 могут хорошо подходить для этой задачи, в зависимости от ваших конкретных потребностей.