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

по данным этой онлайн-книги, the 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:

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

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

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

3 ответов


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

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

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

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

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


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

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

и Volatile-write выглядит так:

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

- инструкции MemoryBarrier(); Это тот, который предотвращает переупорядочивание.MemoryBarrier(); обеспечивает что intructions перед исполнены перед intructions после. Когда 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;

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