Я провел много недель, делая многопоточное кодирование в С# 4.0. Однако для меня остается один вопрос, который остается без ответа.
Я понимаю, что ключевое слово volatile не позволяет компилятору хранить переменные в регистрах, тем самым избегая непреднамеренного чтения устаревших значений. Записи всегда неустойчивы в .Net, поэтому любая документация, в которой указывается, что она также избегает записи сталий, является избыточной.
Я также знаю, что оптимизация компилятора несколько "непредсказуема". Следующий код иллюстрирует стойло из-за оптимизации компилятора (при запуске компиляции релиза вне VS):
class Test
{
public struct Data
{
public int _loop;
}
public static Data data;
public static void Main()
{
data._loop = 1;
Test test1 = new Test();
new Thread(() =>
{
data._loop = 0;
}
).Start();
do
{
if (data._loop != 1)
{
break;
}
//Thread.Yield();
} while (true);
// will never terminate
}
}
Код ведет себя так, как ожидалось. Однако, если я раскомментирую //Thread.Yield(); line, то цикл выйдет.
Кроме того, если я поставлю оператор Sleep перед циклом do, он будет завершен. Я не понимаю.
Естественно, что украшение _loop с volatile также приведет к завершению цикла (в показанном шаблоне).
Мой вопрос: каковы правила, которые следует соблюдать, чтобы определить, когда имплицировать выполнение изменчивого чтения? И почему я могу получить цикл, чтобы выйти с тем, что я считаю нечетными?
ИЗМЕНИТЬ
IL для кода, как показано (киоски):
L_0038: ldsflda valuetype ConsoleApplication1.Test/Data ConsoleApplication1.Test::data
L_003d: ldfld int32 ConsoleApplication1.Test/Data::_loop
L_0042: ldc.i4.1
L_0043: beq.s L_0038
L_0045: ret
IL с выходом() (не останавливается):
L_0038: ldsflda valuetype ConsoleApplication1.Test/Data ConsoleApplication1.Test::data
L_003d: ldfld int32 ConsoleApplication1.Test/Data::_loop
L_0042: ldc.i4.1
L_0043: beq.s L_0046
L_0045: ret
L_0046: call bool [mscorlib]System.Threading.Thread::Yield()
L_004b: pop
L_004c: br.s L_0038