Почему C# volatile не защищает перезапись чтения-записи?

Согласно этой онлайн-книге, volatile Ключевое слово в C# не защищает от переупорядочения операций записи, за которыми следуют операции чтения. Это дает этот пример, в котором оба a а также b может быть установлен в 0, несмотря на x а также y являющийся volatile:

class IfYouThinkYouUnderstandVolatile
{
  volatile int x, y;

  void Test1()        // Executed on one thread
  {
    x = 1;            // Volatile write (release-fence)
    int a = y;        // Volatile read (acquire-fence)
    ...
  }

  void Test2()        // Executed on another thread
  {
    y = 1;            // Volatile write (release-fence)
    int b = x;        // Volatile read (acquire-fence)
    ...
  }
}

Это похоже на то, что сказано в спецификации в 10.5.3:

Чтение изменчивого поля называется изменчивым чтением. Изменчивое чтение имеет "приобретенную семантику"; то есть гарантированно произойдет до любых ссылок на память, которые происходят после нее в последовательности команд.

Запись изменяемого поля называется изменяемой записью. Изменчивая запись имеет "семантику релиза"; то есть это гарантированно произойдет после любых ссылок на память перед инструкцией записи в последовательности команд.

Что является причиной этого? Есть ли случай использования, в котором мы не возражаем против переупорядочения операций записи-чтения?

3 ответа

Решение

Volatile не гарантирует, что операции чтения и записи не будут переупорядочены, а только гарантирует, что операции чтения получат самое последнее значение (без кэширования).

http://msdn.microsoft.com/en-us/library/x13ttww7%28v=vs.71%29.aspx

Система всегда считывает текущее значение энергозависимого объекта в точке, в которой оно запрашивается, даже если предыдущая инструкция запрашивала значение из того же объекта. Также значение объекта пишется сразу при назначении.

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

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

Предотвращение volatile write с volatile read переупорядочение будет довольно дорогим для архитектуры x86/x64. Это из-за оптимизации записи под названием store buffers, Java пошла по этому пути, и изменчивые записи в Java являются фактически полными барьерами памяти на уровне инструкций процессора.

Может быть, слишком поздно, чтобы ответить... но я огляделся и увидел это. Volatile-read - это вызов метода, подобного следующему:

public static int VolatileRead(ref int address)
{
    int num = address;
    Thread.MemoryBarrier();
    return num;
}

А Volatile-напиши вот так:

public static int VolatileWrite(ref int address, int value)
{
    Thread.MemoryBarrier();
    adrdress = value;
}

Инструкция MemoryBarrier(); это тот, который мешает изменить порядок.MemoryBarrier(); обеспечивает выполнение операций до и после операций. Когда VW, то VR, вы будете иметь:

Thread.MemoryBarrier();
adrdress = value; //this line may be reordered with the one bellow
int num = address;//this line may be reordered with the one above
Thread.MemoryBarrier();
return num;
Другие вопросы по тегам