Чтения летучих полей имеют приобретают семантику . Это означает, что гарантируется, что чтение из памяти энергозависимой переменной произойдет до того, как произойдет любое последующее чтение из памяти. Он блокирует компилятор от выполнения переупорядочения, и, если аппаратное обеспечение требует этого (слабо упорядоченный ЦП), он будет использовать специальную инструкцию для сброса аппаратного обеспечения любых чтений, которые происходят после энергозависимого чтения, но спекулятивно запущены рано, или ЦП во-первых, не допустить их раннего выпуска, предотвращая возникновение спекулятивной нагрузки между вопросом о приобретении нагрузки и ее списанием.
Запись летучих полей имеет семантика выпуска . Это означает, что гарантируется, что любая запись в память в энергозависимую переменную будет отложена до тех пор, пока все предыдущие записи в память не будут видны другим процессорам.
Рассмотрим следующий пример:
something.foo = new Thing();
Если foo
является переменной-членом в классе, и другие процессоры имеют доступ к экземпляру объекта, на который ссылается something
, они могут увидеть значение foo
change до записи в память в конструкторе Thing
видны глобально! Вот что означает «слабо упорядоченная память». Это может произойти, даже если компилятор имеет все хранилища в конструкторе до сохранения до foo
. Если foo
равно volatile
, то сохранение в foo
будет иметь семантику выпуска, и аппаратное обеспечение гарантирует, что все записи до записи в foo
будут видны другим процессорам, прежде чем разрешить запись в foo
для происходят.
Как возможно, что записи в foo
так плохо переупорядочиваются? Если строка кеша, содержащая foo
, находится в кеше, и хранилища в конструкторе пропустили кеш, то хранилище может завершиться гораздо раньше, чем пропустит запись в кеш.
(Ужасная) архитектура Itanium от Intel имела слабо упорядоченную память. Процессор, использованный в оригинальной XBox 360, имел слабо упорядоченную память. Многие процессоры ARM, в том числе очень популярный ARMv7-A, имеют слабо упорядоченную память.
Разработчики часто не видят этих гонок данных, потому что такие вещи, как блокировки, будут создавать полный барьер памяти, по сути то же самое, что и семантика получения и выпуска одновременно. Никакие нагрузки внутри замка не могут быть спекулятивно выполнены до того, как замок получен, они задерживаются до тех пор, пока замок не будет получен. Запрещается задерживать хранилища при снятии блокировки, инструкция по снятию блокировки задерживается до тех пор, пока все записи, сделанные внутри блокировки, не будут видны глобально.
Более полным примером является шаблон «Двойная проверка блокировки». Цель этого шаблона - избежать необходимости всегда захватывать блокировку для ленивой инициализации объекта.
Выловлено из Википедии:
public class MySingleton {
private static object myLock = new object();
private static volatile MySingleton mySingleton = null;
private MySingleton() {
}
public static MySingleton GetInstance() {
if (mySingleton == null) { // 1st check
lock (myLock) {
if (mySingleton == null) { // 2nd (double) check
mySingleton = new MySingleton();
// Write-release semantics are implicitly handled by marking mySingleton with
// 'volatile', which inserts the necessary memory barriers between the constructor call
// and the write to mySingleton. The barriers created by the lock are not sufficient
// because the object is made visible before the lock is released.
}
}
}
// The barriers created by the lock are not sufficient because not all threads will
// acquire the lock. A fence for read-acquire semantics is needed between the test of mySingleton
// (above) and the use of its contents.This fence is automatically inserted because mySingleton is
// marked as 'volatile'.
return mySingleton;
}
}
В этом примере хранилища в конструкторе MySingleton
могут быть невидимы для других процессоров до сохранения до mySingleton
. Если это произойдет, другие потоки, которые смотрят на mySingleton, не получат блокировку, и они не обязательно будут получать записи в конструктор.
volatile
никогда не мешает кешированию. Это гарантирует порядок, в котором «видят» другие процессоры. Выпуск хранилища будет задерживать хранилище до тех пор, пока не будут завершены все ожидающие записи, и не будет запущен цикл шины, который говорит другим процессорам отбросить / записать обратно свою строку кэша, если им случится кэширование соответствующих строк. Приобретение загрузки сбрасывает все предполагаемые чтения, гарантируя, что они не будут устаревшими значениями из прошлого.