энергозависимый (компьютерное программирование) - volatile (computer programming)

редактировать

В компьютерном программировании, особенно в C, C ++, C# и Java языков программирования, ключевое слово volatile указывает, что значение может меняться при разных доступах, даже если оно не кажутся измененными. Это ключевое слово предотвращает оптимизирующий компилятор от оптимизации последующих операций чтения или записи и, таким образом, неправильного повторного использования устаревшего значения или исключения записи. Неустойчивые значения в первую очередь возникают при доступе к оборудованию (ввод-вывод с отображением в память ), где чтение или запись в память используются для связи с периферийными устройствами, а в потоковой передаче, где другой поток мог изменить значение.

Несмотря на то, что это общее ключевое слово, поведение volatileзначительно различается между языками программирования, и его легко понять неправильно. В C и C ++ это квалификатор типа , например const , и свойство типа . Более того, в C и C ++ он не работает в большинстве сценариев многопоточности, и такое использование не рекомендуется. В Java и C # это свойство переменной и указывает, что объект , к которому привязана переменная, может изменяться и специально предназначен для потоковой передачи. В языке программирования D есть отдельное ключевое слово sharedдля использования потоковой передачи, но ключевое слово volatileотсутствует.

Содержание
  • 1 В C и C ++
    • 1.1 Пример ввода-вывода с отображением памяти в C
    • 1.2 Сравнение оптимизации в C
    • 1.3 C ++ 11
  • 2 В Java
  • 3 В C #
  • 4 В Fortran
  • 5 Ссылки
  • 6 Внешние ссылки
В C и C ++

В C и, следовательно, C ++, ключевое слово volatileбыл предназначен для

Операции с переменными volatileне являются атомарными, и они не устанавливают правильное выполнение - перед отношениями для многопоточности. Это указано в соответствующих стандартах (C, C ++, POSIX, WIN32), и изменчивые переменные не являются потокобезопасными в подавляющем большинстве текущих реализаций. Таким образом, использование ключевого слова volatileв качестве переносимого механизма синхронизации не приветствуется многими группами C / C ++.

Пример отображаемого в память ввода-вывода в C

In В этом примере код устанавливает значение, хранящееся в foo, равным 0. Затем он начинает опрашивать это значение несколько раз, пока оно не изменится на 255:

static int foo; пустая панель (void) {foo = 0; в то время как (foo! = 255); }

Оптимизирующий компилятор заметит, что никакой другой код не сможет изменить значение, хранящееся в foo, и предположит, что оно останется равным 0всегда. Поэтому компилятор заменит тело функции бесконечным циклом , подобным этому:

void bar_optimized (void) {foo = 0; пока (правда); }

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

Чтобы компилятор не оптимизировал код, как указано выше, используется ключевое слово volatile:

static volatile int foo; пустая панель (void) {foo = 0; в то время как (foo! = 255); }

С этой модификацией условие цикла не будет оптимизировано, и система обнаружит изменение, когда оно произойдет.

Как правило, на платформах (которые представлены в C ++ 11) доступны операции с ограничением памяти, которые должны быть предпочтительнее, чем изменчивые, поскольку они позволяют компилятору выполнять лучшую оптимизацию и многое другое. что важно, они гарантируют правильное поведение в многопоточных сценариях; ни спецификация C (до C11), ни спецификация C ++ (до C ++ 11) не определяет многопоточную модель памяти, поэтому volatile может не вести себя детерминированно в разных операционных системах / компиляторах / процессорах).

Сравнение оптимизации в C

Следующие программы на C и сопутствующие сборки демонстрируют, как ключевое слово volatileвлияет на вывод компилятора. Компилятором в данном случае был GCC.

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

Сравнение сборок
Без volatilekeywordС volatileключевое слово
# include int main () {/ * Эти переменные будут никогда не создаваться в стеке * / int a = 10, b = 100, c = 0, d = 0; / * "printf" будет вызываться с аргументами "% d" и 110 (компилятор вычисляет сумму a + b), следовательно, никаких накладных расходов на выполнение сложения во время выполнения * / printf ("% d", a + b); / * Этот код будет удален при оптимизации, но влияние того, что 'c' и 'd' становятся 100, можно увидеть при вызове «printf» * / a = b; c = b; d = b; / * Компилятор сгенерирует код, в котором printf вызывается с аргументами «% d» и 200 * / printf («% d», c + d); возврат 0; }
# include int main () {volatile int a = 10, b = 100, c = 0, d = 0; printf ("% d", a + b); а = б; c = b; d = b; printf ("% d", c + d); возврат 0; }
gcc -S -O3 -masm = intel noVolatileVar.c -o without.sgcc -S -O3 -masm = intel VolatileVar.c -o with.s
.file "noVolatileVar.c ".intel_syntax noprefix.section.rodata.str1.1," aMS ", @ progbits, 1.LC0:.string"% d ".section.text.startup," ax ", @ progbits.p2align 4,, 15. globl main.type main, @function main:.LFB11:.cfi_startproc sub rsp, 8.cfi_def_cfa_offset 16 mov esi, 110 mov edi, OFFSET FLAT:.LC0 xor eax, eax call printf mov esi, 200 mov edi, OFFSET FLAT:.LC0 xor eax, eax call printf xor eax, eax add rsp, 8.cfi_def_cfa_offset 8 ret.cfi_endproc.LFE11:.size main,.-Main.ident "GCC: (GNU) 4.8.2".section.note.GNU -stack, "", @ progbits
.file "VolatileVar.c".intel_syntax noprefix.section.rodata.str1.1, "aMS", @ progbits, 1.LC0:.string "% d".section. text.startup, "ax", @ progbits.p2align 4,, 15.globl main.type main, @function main:.LFB11:.cfi_startproc sub rsp, 24.cfi_def_cfa_offset 32 ​​mov edi, OFFSET FLAT:.LC0 mov DWORD PTR [rsp], 10 mov DWORD PTR [rsp + 4], 100 mov D WORD PTR [rsp + 8], 0 mov DWORD PTR [rsp + 12], 0 mov esi, DWORD PTR [rsp] mov eax, DWORD PTR [rsp + 4] add esi, eax xor eax, eax call printf mov eax, DWORD PTR [rsp + 4] mov edi, OFFSET FLAT:.LC0 mov DWORD PTR [rsp], eax mov eax, DWORD PTR [rsp + 4] mov DWORD PTR [rsp + 8], eax mov eax, DWORD PTR [rsp +4] mov DWORD PTR [rsp + 12], eax mov esi, DWORD PTR [rsp + 8] mov eax, DWORD PTR [rsp + 12] add esi, eax xor eax, eax call printf xor eax, eax add rsp, 24.cfi_def_cfa_offset 8 ret.cfi_endproc.LFE11:.size main,.-Main.ident "GCC: (GNU) 4.8.2".section.note.GNU-stack, "", @ progbits

C ++ 11

Согласно стандарту C ++ 11 ISO ключевое слово volatile предназначено только для использования для доступа к оборудованию; не используйте его для межпотокового взаимодействия. Для межпотокового взаимодействия стандартная библиотека предоставляет шаблоны std::atomic.

В Java

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

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

Использование volatileможет быть быстрее, чем блокировка, но не будет работать в некоторые ситуации до Java 5. Диапазон ситуаций, в которых действует volatile, был расширен в Java 5; в частности, блокировка с двойной проверкой теперь работает правильно.

В C #

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

Только следующие типы могут быть помечены volatile: все ссылочные типы, Single, Логический, Байт, SByte, Int16, UInt16, Int32, UInt32, Charи все перечисляемые типы с базовым типом Byte, SByte, Int16, UInt16, Int32или UInt32. (Это исключает структуры значения , а также примитивные типы Double, Int64, UInt64и Decimal.)

Использование ключевого слова volatileне поддерживает поля, которые передаются по ссылке или захваченные локальные переменные ; в этих случаях вместо них должны использоваться Thread.VolatileReadи Thread.VolatileWrite.

По сути, эти методы отключают некоторые оптимизации, обычно выполняемые компилятором C #, JIT-компилятор или сам процессор. Гарантии, предоставляемые Thread.VolatileReadи Thread.VolatileWrite, представляют собой надмножество гарантий, предоставляемых ключевым словом volatile: вместо создания "полузаборы" ( т.е. блокировка получения предотвращает только переупорядочение инструкций и кеширование, которое предшествует этому), VolatileReadи VolatileWriteгенерируют "полную блокировку", которая предотвращает переупорядочение инструкций и кеширование этого поля в обоих направлениях. Эти методы работают следующим образом:

  • Метод Thread.VolatileWriteпринудительно записывает значение в поле в момент вызова. Кроме того, любые более ранние загрузки и сохранения программного порядка должны происходить до вызова VolatileWrite, а любые последующие загрузки и сохранения программного порядка должны происходить после вызова.
  • Thread.VolatileReadприводит к принудительному считыванию значения в поле в момент вызова. Кроме того, любые более ранние загрузки и сохранения программного порядка должны происходить до вызова VolatileRead, а любые последующие загрузки и сохранения программного порядка должны происходить после вызова.

Thread.VolatileReadи Thread.VolatileWriteгенерируют полный забор путем вызова метода Thread.MemoryBarrier, который создает барьер памяти, работающий в обоих направлениях. В дополнение к приведенным выше мотивам использования полного ограждения, одна потенциальная проблема с ключевым словом volatile, которая решается путем использования полного ограждения, сгенерированного Thread.MemoryBarrier, заключается в следующем: due Из-за асимметричной природы полузаборов, поле volatileс инструкцией записи, за которой следует инструкция чтения, может все еще иметь порядок выполнения, измененный компилятором. Поскольку полные ограждения симметричны, это не проблема при использовании Thread.MemoryBarrier.

В Fortran

VOLATILEявляется частью стандарта Fortran 2003, хотя более ранняя версия поддерживала его как расширение. Создание всех переменных volatileв функции также полезно для поиска ошибок, связанных с псевдонимом.

целое, изменчивое :: i! Если volatile не определен, следующие две строки кода идентичны write (*, *) i ** 2! Один раз загружает переменную i из памяти и умножает это значение на само значение write (*, *) i * i! Дважды загружает переменную i из памяти и умножает эти значения.

Всегда "углубляясь" в память VOLATILE, компилятор Fortran не может переупорядочивать чтение или запись в volatile. Это делает видимыми для других потоков действия, выполняемые в этом потоке, и наоборот.

Использование VOLATILE уменьшает и даже может предотвратить оптимизацию.

Ссылки
Внешние ссылки
Последняя правка сделана 2021-06-18 05:02:13
Содержание доступно по лицензии CC BY-SA 3.0 (если не указано иное).
Обратная связь: support@alphapedia.ru
Соглашение
О проекте