Подтвердить что ты не робот

Где размещать ограждения/барьеры памяти, чтобы гарантировать свежие чтения/записи?

Как и многие другие люди, меня всегда путают волатильные чтения/записи и заборы. Итак, теперь я пытаюсь полностью понять, что они делают.

Таким образом, волатильное чтение должно (1) демонстрировать семантику получения и (2) гарантировать, что прочитанное значение является свежим, то есть оно не является кешированным значением. Пусть сосредоточиться на (2).

Теперь, я прочитал, что, если вы хотите выполнить изменчивое чтение, вам следует ввести забор (или полный забор) после чтения, например:

int local = shared;
Thread.MemoryBarrier();

Как именно это мешает операции чтения использовать ранее кэшированное значение? В соответствии с определением ограждения (никакие чтения/магазины не могут быть перемещены выше/ниже забора), я бы вставлял забор перед чтением, не позволяя чтению переходить через забор и перемещаться назад во времени (ака, будучи кэшируется).

Как предотвращение перемотки чтения вовремя (или последующие инструкции от перемещения назад во времени) гарантируют неизменное (новое) чтение? Как это помогает?


Точно так же я считаю, что волатильная запись должна вводить забор после операции записи, не позволяя процессору перемещать запись вперед во времени (иначе, задерживая запись). Я считаю, что это приведет к тому, что процессор запустит запись в основную память.

Но, к моему удивлению, реализация С# представляет забор перед записью!

[MethodImplAttribute(MethodImplOptions.NoInlining)] // disable optimizations
public static void VolatileWrite(ref int address, int value)
{
    MemoryBarrier(); // Call MemoryBarrier to ensure the proper semantic in a portable way.
    address = value;
}

Обновление

В соответствии с этим примером, по-видимому, взятым из "С# 4 в двух словах", забор 2, помещенный после того, как запись, как предполагается, заставит запись стираться основная память немедленно, а забор 3, помещенный перед чтением, должен гарантировать свежее чтение:

class Foo{
  int _answer;
  bool complete;
  void A(){
    _answer = 123;
    Thread.MemoryBarrier(); // Barrier 1
    _complete = true;
    Thread.MemoryBarrier(); // Barrier 2
  }
  void B(){
    Thread.MemoryBarrier(); // Barrier 3;
    if(_complete){
      Thread.MemoryBarrier(); // Barrier 4;
      Console.WriteLine(_answer);
    }
  }
}

Идеи в этой книге (и мои собственные личные убеждения), кажется, противоречат идеям реализаций С# VolatileRead и VolatileWrite.

4b9b3361

Ответ 1

Как именно это предотвращает использование операции чтения ранее кэшированное значение?

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

Как предотвращается перемещение считывания вперед во времени (или последующие инструкции перемещаются назад во времени). неустойчивое (свежее) чтение? Как это помогает?

Будьте осторожны с терминологией здесь. Летучий не синоним свежего. Как я уже упоминал выше, его реальная полезность заключается в том, как два или более неустойчивых чтения соединены вместе. Следующее чтение в последовательности изменчивых чтений будет абсолютно возвращать более новое значение, чем предыдущее чтение того же адреса. Код без блокировки должен быть написан с учетом этой предпосылки. То есть, код должен быть структурирован для работы над принципом работы с более новым значением, а не с последним значением. Вот почему большинство незакрепленных кодов вращается в цикле, пока он не сможет убедиться, что операция полностью успешно.

Идеи в этой книге (и мои собственные личные убеждения) кажутся противоречат идеям С# VolatileRead и VolatileWrite реализации.

Не совсем. Помните volatile!= Свежий. Да, если вы хотите "свежее" чтение, тогда вам нужно поместить забор перед чтением. Но это не то же самое, что делать волатильное чтение. Я говорю, что если реализация VolatileRead имела вызов Thread.MemoryBarrier перед инструкцией чтения, то это фактически не приводило бы к изменчивому чтению. Если бы произвело бы свежее чтение, хотя.

Ответ 2

Важно понимать, что volatile означает не только "не может кэшировать значение", но также дает важные гарантии видимости (точнее, вполне возможно, что волатильная запись, которая поступает только в кеш, зависит исключительно на аппаратное обеспечение и используемые протоколы когерентности кэш-памяти)

Волатильное чтение дает семантику получения, а волатильная запись имеет семантику выпуска. Захват означает, что вы не можете переупорядочить чтение или запись перед забором, в то время как забор забора означает, что вы не можете переместить их после забора. связанный ответ в комментариях объясняет, что на самом деле довольно красиво.

Теперь вопрос в том, что если у нас нет какой-либо барьер памяти перед загрузкой, как бы гарантировано, что мы увидим новейшее значение? Ответ на это: потому что мы также помещаем барьеры памяти после каждой неустойчивой записи, чтобы гарантировать это.

Doug Lea написал замечательное резюме о том, какие барьеры существуют, что они делают и куда их переносить на неустойчивые чтения/записи для JMM в качестве помощи для авторов компиляторов, но текст также весьма полезен для других людей. Волатильные чтения и записи дают одинаковые гарантии как в Java, так и в CLR, что обычно применимо.

Источник - прокрутите вниз до раздела "Блокировки памяти" (я бы скопировал интересные части, но форматирование не сохранилось это..)