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

Как я могу понять, какие барьеры памяти и неустойчивые

Некоторые языки предоставляют модификатор volatile, который описывается как выполняющий "барьер памяти чтения" перед чтением памяти, которая поддерживает переменную.

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

Итак, действительно ли volatile действительно гарантирует, что обновленное значение будет считано или просто (gasp!), что считанные значения, по крайней мере, настолько же актуальны, как и перед барьером? Или какая-то другая интерпретация? Каковы практические последствия этого ответа?

4b9b3361

Ответ 1

Существуют барьеры чтения и барьеры для записи; приобретать барьеры и барьеры для выпуска. И еще (io vs memory и т.д.).

Барьеры не существуют, чтобы контролировать "последнее" значение или "свежесть" значений. Они предназначены для управления относительным упорядочением доступа к памяти.

Написание барьеров управляет порядком записи. Поскольку записи в память медленные (по сравнению со скоростью процессора), обычно существует очередь запросов на запись, в которой записи отправляются до того, как они действительно произойдут. Хотя они поставлены в очередь по порядку, а внутри очереди записи могут быть переупорядочены. (Так что, может быть, "очередь" - не лучшее имя...) Если вы не используете барьеры записи, чтобы предотвратить переупорядочение.

Чтение барьеров контролирует порядок чтения. Из-за спекулятивного исполнения (процессор смотрит вперед и загружается из памяти раньше) и из-за существования буфера записи (CPU будет считывать значение из буфера записи вместо памяти, если оно есть, то есть процессор думает, что он просто написал X = 5, то зачем читать его обратно, просто убедитесь, что он все еще ждет, чтобы он стал 5 в буфере записи). Чтение может случиться не в порядке.

Это верно, независимо от того, что пытается сделать компилятор в отношении порядка сгенерированного кода. т.е. "volatile" в С++ здесь не поможет, поскольку он только сообщает компилятору вывести код для повторного чтения значения из "памяти", он НЕ говорит CPU о том, как/где его читать (т.е. "память" ) это много вещей на уровне ЦП).

Таким образом, преграды чтения/записи помещают блоки для предотвращения переупорядочения в очередях чтения/записи (чтение обычно не является частью очереди, но эффекты переупорядочения одинаковы).

Какие блоки? - приобретать и/или выпускать блоки.

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

Release - например, write-release (x, 5) сначала запустит (или отметит) очередь, затем добавит запрос на запись в очередь записи. Таким образом, более ранние записи не будут переупорядочены, чтобы произойти после x = 5, но обратите внимание, что последующие записи могут быть переупорядочены до x = 5.

Обратите внимание, что я спарил чтение с получением и записью с выпуском, потому что это типично, но возможны разные комбинации.

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

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

Как правило, для программирования без блокировки или С# или java 'volatile', то, что вы хотите/ чтение-запись и запись-релиз.

т

void threadA()
{
   foo->x = 10;
   foo->y = 11;
   foo->z = 12;
   write_release(foo->ready, true);
   bar = 13;
}
void threadB()
{
   w = some_global;
   ready = read_acquire(foo->ready);
   if (ready)
   {
      q = w * foo->x * foo->y * foo->z;
   }
   else
       calculate_pi();
}

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

После того, как threadA() выполнит запись foo, ему нужно написать foo- > ready LAST, действительно последний, иначе другие потоки могут увидеть foo- > ready early и получить неправильные значения x/y/z. Таким образом, мы используем write_release в foo- > ready, который, как упоминалось выше, эффективно "сбрасывает" очередь записи (обеспечивая фиксацию x, y, z), затем добавляет запрос ready = true в очередь. А затем добавляет запрос bar = 13. Обратите внимание, что, поскольку мы просто использовали барьер для выпуска (не полный), бар = 13 может быть написан до готовности. Но нам все равно! т.е. мы предполагаем, что бар не изменяет общие данные.

Теперь threadB() должен знать, что, когда мы говорим "готово", мы действительно подразумеваем готовность. Итак, мы делаем read_acquire(foo->ready). Это чтение добавляется в очередь чтения, после чего очередь размывается. Обратите внимание, что w = some_global также может оставаться в очереди. Поэтому foo- > ready можно прочитать до some_global. Но опять же, нам все равно, поскольку это не часть важных данных, о которых мы так осторожны. Нас интересует foo- > x/y/z. Таким образом, они добавляются в очередь чтения после флеша/маркера получения, гарантируя, что они читаются только после чтения foo- > ready.

Заметим также, что это, как правило, те же самые барьеры, используемые для блокировки и разблокировки мьютекса/CriticalSection/и т.д. (т.е. приобретать при блокировке(), освобождать при разблокировке()).

Итак,

  • Я уверен, что это (т.е. получение/выпуск) - это именно то, что говорят MS docs для чтения/записи "изменчивых" переменных в С# (и, возможно, для MS С++, но это нестандартно), См. http://msdn.microsoft.com/en-us/library/aa645755(VS.71).aspxв том числе "Волатильное чтение имеет" приобретает семантику ", то есть оно гарантировано произойдет до любых ссылок на память, которые происходят после него..."

  • Я думаю, что java - это то же самое, хотя я не так привык. Я подозреваю, что это точно так же, потому что вам просто не требуется больше гарантий, чем чтение-запись/запись-релиз.

  • В вашем вопросе вы были на правильном пути, думая, что это действительно все относительно относительного порядка - вы просто имели заказы назад (т.е. "прочитанные значения, по крайней мере, настолько же актуальны, как и читается перед барьером?" - нет, читается до того, как барьер неважен, его читают ПОСЛЕ барьера, гарантированного ПОСЛЕ, наоборот, для записи).

  • И обратите внимание, что, как уже упоминалось, переупорядочение происходит как при чтении, так и в записи, поэтому используйте только барьер на одном потоке, а не другой, НЕ РАБОТАЙТЕ. т.е. освобождение от записи недостаточно, без считывания. т.е. даже если вы напишете его в правильном порядке, его можно будет прочитать в неправильном порядке, если вы не используете барьеры чтения, чтобы идти с барьерами записи.

  • И, наконец, обратите внимание, что блокировка программирования и архитектуры памяти CPU может быть на самом деле намного сложнее, чем это, но приклеивание с получением/выпуском доставит вам довольно далеко.

Ответ 2

volatile в большинстве языков программирования не подразумевает реальный предел памяти для чтения в CPU, а заказ компилятору не оптимизировать чтение через кеширование в регистре. Это означает, что процесс чтения/поток получит значение "в конечном итоге". Общей методикой является объявление булевого volatile флага, который должен быть установлен в обработчике сигнала и проверен в основном программном цикле. Напротив, блокировки памяти ЦП непосредственно предоставляются либо с помощью инструкций CPU, либо подразумеваются с помощью некоторых ячеек-ассемблеров (например, префикс lock в x86) и используются, например, при разговоре с аппаратными устройствами, где порядок чтения и записи в регистры ввода-вывода с отображением памяти важно или синхронизирует доступ к памяти в среде многопроцессорной обработки. Чтобы ответить на ваш вопрос - нет, барьер памяти не гарантирует "последнее" значение, но гарантирует порядок операций доступа к памяти. Это важно, например, в без блокировки.

Здесь является одним из праймеров на барьерах памяти CPU.