Для чего используется ключевое слово "volatile"? - PullRequest
113 голосов
/ 07 августа 2010

Я прочитал несколько статей о ключевом слове volatile, но не смог понять, как оно правильно используется.Не могли бы вы сказать мне, для чего он должен использоваться в C # и в Java?

Ответы [ 7 ]

151 голосов
/ 07 августа 2010

Рассмотрим этот пример:

int i = 5;
System.out.println(i);

Компилятор может оптимизировать это, чтобы вывести только 5, например:

System.out.println(5);

Однако, если есть другая нить, которая может изменить i это неправильное поведение.Если другой поток изменит i на 6, оптимизированная версия будет по-прежнему печатать 5.

Ключевое слово volatile предотвращает такую ​​оптимизацию и кэширование и поэтому полезно, когда переменная может быть изменена другим потоком.

80 голосов
/ 07 августа 2010

Как для C #, так и для Java «volatile» сообщает компилятору, что значение переменной никогда не должно кэшироваться, поскольку ее значение может изменяться вне области самой программы.Компилятор тогда избежит любых оптимизаций, которые могут привести к проблемам, если переменная изменится «вне контроля».

35 голосов
/ 07 марта 2013

Чтобы понять, что volatile делает с переменной, важно понимать, что происходит, когда переменная не является volatile.

  • Переменная является энергонезависимой

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

  • Переменная является летучей

Когда переменные объявляются как volatile, это, по сути, означает, что потоки не должны кэшировать такую ​​переменную или, другими словами, потоки не должны доверять значениям этих переменных, если они не считываются напрямую из основной памяти.

Итак, когда сделать переменную изменчивой?

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

33 голосов
/ 07 августа 2010

ключевое слово volatile имеет разные значения как в Java, так и в C #.

Java

Из спецификации языка Java :

Поле может быть объявлено как volatile, и в этом случае модель памяти Java гарантирует, что все потоки увидят согласованное значение для переменной.

C #

Из справочника C # по ключевому слову volatile :

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

29 голосов
/ 07 октября 2016

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

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

Рассмотрим следующий пример:

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 никогда не мешает кешированию. Это гарантирует порядок, в котором «видят» другие процессоры. Выпуск хранилища будет задерживать хранилище до тех пор, пока не будут завершены все ожидающие записи, и не будет запущен цикл шины, который говорит другим процессорам отбросить / записать обратно свою строку кэша, если им случится кэширование соответствующих строк. Приобретение загрузки сбрасывает все предполагаемые чтения, гарантируя, что они не будут устаревшими значениями из прошлого.

9 голосов
/ 07 августа 2010

В Java «volatile» используется, чтобы сообщить JVM, что переменная может использоваться несколькими потоками одновременно, поэтому некоторые общие оптимизации не могут быть применены.

В частности, ситуация, когда два потокадоступ к одной и той же переменной выполняется на разных процессорах на одной машине.Обычно процессоры активно кешируют данные, которые в нем хранятся, потому что доступ к памяти намного медленнее, чем доступ к кешу.Это означает, что если данные обновляются в CPU1, они должны немедленно проходить через все кэши и в основную память, а не тогда, когда кэш решает очистить себя, чтобы CPU2 мог видеть обновленное значение (опять же, игнорируя всекеши в пути).

1 голос
/ 29 марта 2018

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...