Volatile-конструкции
Когда компьютеры только появились, программное обеспечение писалось на ассем-
блере. Это было крайне утомительное занятие, так как программист должен был
формулировать все в явном виде: использовать определенный регистр процессора,
передать управление, осуществить косвенный вызов и т. п. Для упрощения задачи
были придуманы языки высокого уровня. Именно в них впервые были реализованы
привычные конструкции
if
/
else
,
switch
/
case
, циклы, локальные переменные,
аргументы, вызовы виртуальных методов, перегрузка операторов и многое другое.
В конечном итоге компилятор преобразует конструкции высокого уровня в низ-
коуровневые, позволяющие компьютеру понять, что именно ему следует делать.
Другими словами, компилятор C# преобразует конструкции языка C# в коман-
ды промежуточного языка (Intermediate Language, IL), которые, в свою очередь,
JIT-компилятор превращает в машинные директивы, обрабатываемые уже непо-
средственно процессором. При этом компилятор C#, JIT-компилятор и даже сам
процессор могут оптимизировать ваш код. К примеру, после компиляции следую-
щий нелепый метод в конечном итоге превратится в ничто:
827
Конструкции.пользовательского.режима
private static void OptimizedAway() {
// Вычисленная во время компиляции константа равна нулю
Int32 value = (1 * 100) – (50 * 2);
// Если значение равно 0, цикл не выполняется
for (Int32 x = 0; x < value; x++) {
// Код цикла не нужно компилировать, так как он никогда не выполняется
Console.WriteLine("Jeff");
}
}
В этом коде компилятор выясняет, что значение всегда равно 0, а значит, цикл
никогда не будет выполнен и соответственно нет никакой нужды компилировать код
внутри него. От метода в итоге может ничего не остаться. Далее, при компиляции
метода, вызывающего метод
OptimizedAway
, JIT-компилятор попытается встроить
туда код метода
OptimizedAway
, но так как этот код отсутствует, компилятор даже
исключит сам код вызова метода. Разработчики очень любят это свойство компи-
ляторов. Обычно код пытаются писать максимально осмысленно; он должен быть
простым для чтения, записи и редактирования. А затем компилятор переводит
его на язык, понятный компьютеру. И мы хотим, чтобы компиляторы делали это
максимально хорошо.
В процессе оптимизации кода компилятором C#, JIT-компилятором и про-
цессором гарантируется сохранение его назначения. То есть с точки зрения одного
потока метод делает то, зачем мы его написали, хотя способ реализации может от-
личаться от описанного в исходном коде. Однако при переходе к многопоточной
конфигурации ситуация может измениться. Вот пример, в котором в результате
оптимизации программа стала работать не так, как ожидалось:
internal static class StrangeBehavior {
// Далее вы увидите, что проблема решается объявлением этого поля volatile
private static Boolean s_stopWorker = false;
public static void Main() {
Console.WriteLine("Main: letting worker run for 5 seconds");
Thread t = new Thread(Worker);
t.Start();
Thread.Sleep(5000);
s_stopWorker = true;
Console.WriteLine("Main: waiting for worker to stop");
t.Join();
}
private static void Worker(Object o) {
Int32 x = 0;
while (!s_stopWorker) x++;
Console.WriteLine("Worker: stopped when x={0}", x);
}
}
828
Do'stlaringiz bilan baham: |