stringtranslate.com

изменчивый (компьютерное программирование)

В программировании значение называется изменчивым , если оно может быть прочитано или изменено асинхронно чем-то, кроме текущего потока выполнения . Значение переменной может спонтанно изменяться по таким причинам, как: совместное использование значений с другими потоками; совместное использование значений с асинхронными обработчиками сигналов ; доступ к аппаратным устройствам через отображенный в память ввод-вывод (где вы можете отправлять и получать сообщения с периферийных устройств , считывая и записывая в память). Поддержка этих вариантов использования значительно различается среди нескольких языков программирования, имеющих ключевое слово. Изменчивость может иметь последствия в отношении соглашений о вызове функций и того, как переменные хранятся, доступны и кэшируются.volatilevolatile

В C и C++

В C и C++ volatileявляется квалификатором типа , например const, и является частью типа ( например, типа переменной или поля).

Поведение volatileключевого слова в C и C++ иногда дается в терминах подавления оптимизаций оптимизирующего компилятора : 1- не удалять существующие volatileчтения и записи, 2- не добавлять новые volatileчтения и записи, и 3- не переупорядочивать volatileчтения и записи. Однако это определение является лишь приблизительным для удобства новых учащихся, и на это приблизительное определение не следует полагаться при написании реального производственного кода.

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

Стандарты C и C++ позволяют писать переносимый код, который разделяет значения между longjmpобъектами volatile, а стандарты позволяют писать переносимый код, который разделяет значения между обработчиками сигналов и остальной частью кода в volatile sig_atomic_tобъектах. Любое другое использование volatileключевого слова в C и C++ по своей сути непереносимо или неверно. В частности, написание кода с volatileключевым словом для отображаемых в память устройств ввода-вывода по своей сути непереносимо и всегда требует глубоких знаний конкретной целевой реализации C/C++ и платформы.

Многопоточность

Распространено заблуждение, что volatileключевое слово полезно в переносимом многопоточном коде в C и C++. volatileКлючевое слово в C и C++ никогда не функционировало как полезный переносимый инструмент для любого многопоточного сценария. [2] [3] [4] [5] В отличие от языков программирования Java и C# , операции над volatileпеременными в C и C++ не являются атомарными , и операции над volatileпеременными не имеют достаточных гарантий упорядочения памяти (т. е. барьеров памяти ). Большинство компиляторов C и C++, компоновщиков и сред выполнения просто не предоставляют необходимых гарантий упорядочения памяти, чтобы сделать volatileключевое слово полезным для любого многопоточного сценария. До стандартов C11 и C++11 программисты были вынуждены полагаться на гарантии от отдельных реализаций и платформ (например, POSIX и WIN32) для написания многопоточного кода. С современными стандартами C11 и C++11 программисты могут писать переносимый многопоточный код, используя новые переносимые конструкции, такие как std::atomic<T>шаблоны. [6]

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

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

статический int foo ;  void bar ( void ) { foo = 0 ;      пока ( фу != 255 ) ; }    

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

void bar_optimized ( void ) { foo = 0 ;      пока ( истина ) ; }  

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

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

статический изменчивый int foo ;   void bar ( void ) { foo = 0 ;       пока ( фу != 255 ) ; }    

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

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

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

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

Дефекты стандартов

Хотя это и подразумевается как C, так и C++, текущий стандарт C не может выразить, что volatileсемантика относится к lvalue, а не к объекту, на который она ссылается. Соответствующий отчет о дефекте DR 476 (для C11) все еще находится на рассмотрении в C17 . [7]

Дефекты компилятора

В отличие от других языковых возможностей C и C++, volatileключевое слово не поддерживается большинством реализаций C/C++ - даже для переносимого использования в соответствии со стандартами C и C++. Большинство реализаций C/C++ содержат ошибки в отношении поведения ключевого слова volatile. [8] [9] Программисты должны быть очень осторожны, когда используют volatileключевое слово в C и C++.

На Яве

Во всех современных версиях языка программирования Java ключевое volatileслово дает следующие гарантии:

Вместе эти гарантии превращаются volatileв полезную многопоточную конструкцию в Java . В частности, типичный алгоритм блокировки с двойной проверкойvolatile работает правильно в Java . [12]

Очень старые версии Java

До Java версии 5 стандарт Java не гарантировал относительного порядка volatileчтения volatileи записи. Другими словами, volatileне имел семантики барьера памяти "acquire" и "release". Это значительно ограничивало его использование в качестве многопоточной конструкции. В частности, типичный алгоритм блокировки с двойной проверкойvolatile с не работал правильно.

В C#

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

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

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

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

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

В Фортране

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

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

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

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

Ссылки

  1. ^ «Публикация в комитете по стандартам C++».
  2. ^ «Volatile Keyword In Visual C++». Microsoft MSDN . 21 сентября 2021 г.
  3. ^ «Документация ядра Linux – Почему не следует использовать класс типа «volatile»». kernel.org .
  4. ^ Скотт Мейерс; Андрей Александреску (2004). «C++ и опасности двойной проверки блокировки» (PDF) . DDJ .
  5. ^ Джереми Эндрюс (2007). "Linux: Volatile Superstition". kerneltrap.org. Архивировано из оригинала 20-06-2010 . Получено 9 января 2011 г.
  6. ^ "volatile (C++)". Microsoft MSDN . 21 сентября 2021 г.
  7. ^ Резюме запроса на разъяснение для C11. Версия 1.13, октябрь 2017 г.
  8. ^ Эйде, Эрик; Регер, Джон (октябрь 2008 г.). «Volatiles неправильно компилируются и что с этим делать» (PDF) . Труды Восьмой международной конференции ACM и IEEE по встроенному программному обеспечению (EMSOFT), Атланта, Джорджия, США – через cs.utah.edu.
  9. ^ «Легколетучие ошибки, три года спустя – внедрены в академическую среду». blog.regehr.org . Получено 28.08.2024 .
  10. ^ Раздел 17.4.4: Порядок синхронизации "Спецификация языка Java®, Java SE 7 Edition". Oracle Corporation . 2013. Получено 12 мая 2013 г.
  11. ^ «Java Concurrency: Understanding the 'Volatile' Keyword». dzone.com. 2021-03-08. Архивировано из оригинала 2021-05-09 . Получено 2021-05-09 .
  12. ^ Нил Коффи. «Двойная проверка блокировки (DCL) и как ее исправить». Javamex . Получено 19 сентября 2009 г.
  13. ^ abcd Albahari, Joseph. "Часть 4: Расширенная потоковая обработка". Threading in C# . O'Reilly Media. Архивировано из оригинала 12 декабря 2019 г. Получено 9 декабря 2019 г.{{cite web}}: CS1 maint: бот: исходный статус URL неизвестен ( ссылка )
  14. ^ Рихтер, Джеффри (11 февраля 2010 г.). "Глава 7: Константы и поля". CLR Via C# . Microsoft Press. стр. 183. ISBN 978-0-7356-2704-8.
  15. ^ Рихтер, Джеффри (11 февраля 2010 г.). «Глава 28: Примитивные конструкции синхронизации потоков». CLR Via C# . Microsoft Press. стр. 797–803. ISBN 978-0-7356-2704-8.
  16. ^ "Атрибут и утверждение VOLATILE". Cray. Архивировано из оригинала 2018-01-23 . Получено 2016-04-22 .
  17. ^ "Изменчивый и разделяемый массив в Фортране". Intel.com .
  18. ^ "ЛЕТУЧЕВЫЙ". Oracle.com .

Внешние ссылки