Я это понимаю volatile
информирует компилятор о том, что значение может быть изменено, но должен ли компилятор для выполнения этой функции вводить забор памяти, чтобы заставить его работать?
Насколько я понимаю, последовательность операций с изменчивыми объектами не может быть переупорядочена и должна быть сохранена. Похоже, это подразумевает, что некоторые ограждения памяти необходимы и что на самом деле нет способа обойти это. Правильно ли я говорю?
Есть интересное обсуждение этого связанного вопроса
... Доступ к различным изменчивым переменным не может быть переупорядочен компилятором, если они встречаются в отдельных полных выражениях ... верно, что volatile бесполезен для безопасности потоков, но не по причинам, которые он приводит. Это не потому, что компилятор может изменить порядок доступа к изменчивым объектам, а потому, что ЦП может их изменить. Атомарные операции и барьеры памяти не позволяют компилятору и процессору переупорядочивать
На что Дэвид Шварц отвечает в комментариях :
... С точки зрения стандарта C ++, нет никакой разницы между компилятором, который что-то делает, и компилятором, выдающим инструкции, которые заставляют оборудование что-то делать. Если ЦП может изменить порядок доступа к летучим объектам, то стандарт не требует сохранения их порядка. ...
... Стандарт C ++ не делает различий в том, что происходит при переупорядочивании. И вы не можете утверждать, что ЦП может изменить их порядок без видимого эффекта, так что это нормально - стандарт C ++ определяет их порядок как наблюдаемый. Компилятор соответствует стандарту C ++ на платформе, если он генерирует код, который заставляет платформу выполнять то, что требует стандарт. Если стандарт требует, чтобы доступ к летучим веществам не переупорядочивался, то платформа, которая переупорядочивает их, не соответствует требованиям. ...
Я хочу сказать, что если стандарт C ++ запрещает компилятору переупорядочивать доступы к отдельным переменным, исходя из теории, что порядок таких обращений является частью наблюдаемого поведения программы, то он также требует, чтобы компилятор испустил код, который запрещает процессору выполнять так. Стандарт не делает различий между тем, что делает компилятор, и тем, что код генерации компилятора заставляет делать ЦП.
Возникает два вопроса: «Правильно» ли кто-то из них? Что на самом деле делают реальные реализации?
источник
volatile
оптимизацию чтения переменных кешами ЦП. Либо все эти компиляторы не соответствуют требованиям, либо стандарт не означает то, что вы думаете. (Стандарт не делает различий между тем, что делает компилятор, и тем, что компилятор заставляет делать ЦП. Задача компилятора - генерировать код, который при запуске соответствует стандарту.)Ответы:
Вместо того, чтобы объяснять, что
volatile
делает, позвольте мне объяснить, когда вам следует использоватьvolatile
.volatile
переменную - это практически единственное, что стандарт позволяет вам делать из обработчика сигналов. Начиная с C ++ 11, вы можете использоватьstd::atomic
для этой цели, но только если атомар не заблокирован.setjmp
Intel .Например:
volatile int *foo = some_memory_mapped_device; while (*foo) ; // wait until *foo turns false
Без
volatile
спецификатора компилятору разрешено полностью оптимизировать цикл. Спецификаторvolatile
сообщает компилятору, что он не может предполагать, что 2 последующих чтения вернут одно и то же значение.Обратите внимание, что
volatile
это не имеет ничего общего с потоками. Приведенный выше пример не работает, если другой поток записывал в*foo
потому что не задействована операция получения.Во всех других случаях использование
volatile
должно рассматриваться как непереносимое и больше не проходить проверку кода, за исключением случаев, когда речь идет о компиляторах до C ++ 11 и расширениях компилятора (таких как/volatile:ms
переключатель msvc , который включен по умолчанию в X86 / I64).источник
setjmp
- это две гарантии, которые обеспечивает стандарт. С другой стороны, целью , по крайней мере с самого начала, была поддержка ввода-вывода с отображением памяти. Что на некоторых процессорах может потребовать ограждения или мембраны.volatile
доступе.Компилятор C ++, соответствующий спецификации, не обязан вводить ограждение памяти. Ваш конкретный компилятор может; направьте свой вопрос авторам вашего компилятора.
Функция "volatile" в C ++ не имеет ничего общего с потоками. Помните, что цель «volatile» - отключить оптимизацию компилятора, чтобы чтение из регистра, которое изменяется из-за внешних условий, не оптимизировалось. Является ли адрес памяти, в который записывается другой поток на другом процессоре, регистром, который изменяется из-за внешних условий? Нет. Опять же, если некоторые авторы компиляторов решили рассматривать адреса памяти, в которые записываются разные потоки на разных процессорах, как если бы регистры менялись из-за внешних условий, это их дело; они не обязаны этого делать. Они также не требуются - даже если это вводит ограждение памяти - например, для обеспечения каждого поток видит согласованный упорядочивания энергозависимых операций чтения и записи.
Фактически, volatile практически бесполезен для многопоточности в C / C ++. Лучше всего избегать этого.
Более того: ограждения памяти - это деталь реализации конкретных архитектур процессоров. В C #, где летучий явно будет предназначен для многопоточного, спецификация не говорит , что половина изгороди будет введена, так как программа может быть запущена на архитектуре , которая не имеет ограждений в первую очередь. Скорее, опять же, спецификация дает определенные (крайне слабые) гарантии того, какие оптимизации будут избегать компилятор, среда выполнения и ЦП, чтобы наложить определенные (чрезвычайно слабые) ограничения на порядок упорядочивания некоторых побочных эффектов. На практике эти оптимизации устраняются за счет использования полузаборов, но это деталь реализации, которая может измениться в будущем.
Тот факт, что вы заботитесь о семантике volatile на любом языке, поскольку они относятся к многопоточности, указывает на то, что вы думаете о совместном использовании памяти между потоками. Подумайте, просто не делайте этого. Это значительно усложняет понимание вашей программы и увеличивает вероятность того, что она будет содержать малозаметные ошибки, которые невозможно воспроизвести.
источник
volatile
был введен в стандарт C. Тем не менее, поскольку стандарт не может указывать такие вещи, как то, что на самом деле происходит при «доступе», он говорит, что «то, что составляет доступ к объекту, который имеет тип с изменяемым типом, определяется реализацией». Слишком много реализаций сегодня не предоставляют полезного определения доступа, что ИМХО нарушает дух стандарта, даже если он соответствует букве.volatile
семантика более сильная, компилятор должен генерировать каждый запрошенный доступ (1.9 / 8, 1.9 / 12), а не просто гарантировать, что в конечном итоге будут обнаружены экзогенные изменения (1.10 / 27). В мире ввода-вывода с отображением в память чтение из памяти может иметь произвольную связанную логику, например средство получения свойства. Вы не будете оптимизировать вызовы методов получения свойств в соответствии с указанными вами правиламиvolatile
, и Стандарт не позволяет этого.Дэвид упускает из виду тот факт, что стандарт C ++ определяет поведение нескольких потоков, взаимодействующих только в определенных ситуациях, а все остальное приводит к неопределенному поведению. Состояние гонки, включающее хотя бы одну запись, не определено, если вы не используете атомарные переменные.
Следовательно, компилятор имеет полное право отказаться от любых инструкций по синхронизации, поскольку ваш ЦП заметит разницу только в программе, которая демонстрирует неопределенное поведение из-за отсутствия синхронизации.
источник
volatile
не имеет ничего общего с нитками; его первоначальная цель заключалась в поддержке ввода-вывода с отображением памяти. И, по крайней мере, на некоторых процессорах для поддержки ввода-вывода с отображением в память потребуются ограждения. (Компиляторы этого не делают, но это другая проблема.)volatile
имеет много общего с потоками:volatile
имеет дело с памятью, к которой можно получить доступ без ведома компилятора, что к ней можно получить доступ, и это охватывает множество реальных способов использования общих данных между потоками на конкретном процессоре.Прежде всего, стандарты C ++ не гарантируют барьеров памяти, необходимых для правильного упорядочивания операций чтения / записи, которые не являются атомарными. Переменные volatile рекомендуется использовать с MMIO, обработкой сигналов и т. д. В большинстве реализаций volatile бесполезно для многопоточности и обычно не рекомендуется.
Что касается реализации изменчивого доступа, это выбор компилятора.
Эта статья , описывающая поведение gcc, показывает, что вы не можете использовать изменчивый объект в качестве барьера памяти для упорядочивания последовательности записи в энергозависимую память.
Что касается поведения icc, я обнаружил, что этот источник также сообщает, что volatile не гарантирует упорядоченного доступа к памяти.
У компилятора Microsoft VS2013 другое поведение. В этой документации объясняется, как volatile обеспечивает семантику Release / Acquire и позволяет использовать изменчивые объекты в блокировках / выпусках в многопоточных приложениях.
Еще один аспект, который необходимо принять во внимание, заключается в том, что один и тот же компилятор может вести себя по- разному. в изменчивую в зависимости от целевой аппаратной архитектуры . В этом сообщении о компиляторе MSVS 2013 четко изложены особенности компиляции с volatile для платформ ARM.
Итак, мой ответ:
будет: Не гарантируется, возможно, нет, но некоторые компиляторы могут это сделать. Не стоит полагаться на то, что это так.
источник
volatile
позволяет компилятору переупорядочивать загрузки / сохранения? Или вы говорите, что этого требует стандарт C ++? А если последнее, можете ли вы ответить на мой аргумент об обратном, приведенный в исходном вопросе?volatile
lvalue. Однако, поскольку определение «доступа» остается на усмотрение реализации, это не дает нам многого, если реализация не заботится.volatile
, но в сгенерированном коде компилятора Visual Studios 2012 нетvolatile
- это то, что специально перечислено стандартом. (setjmp
, сигналы и т. д.)Насколько мне известно, компилятор только вставляет ограждение памяти в архитектуру Itanium.
volatile
Ключевое слово действительно лучше всего использовать для асинхронных изменений, например, обработчиков сигналов и отображенных в памяти регистров; обычно это неподходящий инструмент для многопоточного программирования.источник
volatile
чрезвычайно полезен для многих целей, никогда не связанных с оборудованием. Если вы хотите, чтобы реализация генерировала код ЦП, который точно следует коду C / C ++, используйтеvolatile
.Это зависит от того, какой компилятор «компилятор». Visual C ++ делает это с 2005 года. Но Стандарт не требует этого, поэтому некоторые другие компиляторы этого не требуют.
источник
int volatile i; int main() { return i; }
генерирует главный ровно две инструкции:mov eax, i; ret 0;
.cl /help
говорит версия 18.00.21005.1. Каталог, в котором он находитсяC:\Program Files (x86)\Microsoft Visual Studio 12.0\VC
. В заголовке командного окна написано VS 2013. Итак, что касается версии ... Я использовал только следующие параметры/c /O2 /Fa
. (Без/O2
него также устанавливается локальный фрейм стека. Но инструкции по ограждению все еще нет.)main
, чтобы компилятор мог видеть всю программу и знать, что не было других потоков или, по крайней мере, никаких других обращений к переменной до меня (так что не могло быть проблем с кешем), может повлиять на это тоже, но почему-то я в этом сомневаюсь.Это в основном из памяти и основано на pre-C ++ 11, без потоков. Но, участвуя в обсуждениях в комитете, я могу сказать, что у комитета никогда не было намерения, чтобы
volatile
можно было бы использовать для синхронизации между потоками. Microsoft предложила это, но предложение не получилось.Ключевая спецификация
volatile
заключается в том, что доступ к изменчивому объекту представляет собой «наблюдаемое поведение», как и ввод-вывод. Точно так же компилятор не может изменить порядок или удалить определенный ввод-вывод, он не может изменить порядок или удалить доступ к изменчивому объекту (или, точнее, доступ через выражение lvalue с изменчивым квалифицированным типом). Первоначальная цель volatile заключалась в том, чтобы поддерживать ввод-вывод с отображением памяти. Однако «проблема» в том, что это определяется реализацией, что составляет «изменчивый доступ». И многие компиляторы реализуют это так, как если бы определение было «выполнена инструкция, которая читает или записывает в память». Это законное, хотя и бесполезное определение, если это указано в реализации. (Мне еще не удалось найти фактическую спецификацию для любого компилятора.Возможно (и это аргумент, который я принимаю), это нарушает намерение стандарта, поскольку, если оборудование не распознает адреса как ввод-вывод с отображением памяти и не запрещает любое переупорядочение и т. Д., Вы даже не можете использовать volatile для ввода-вывода с отображением памяти, по крайней мере, на архитектурах Sparc или Intel. Тем не менее, ни один из компиляторов, на которые я смотрел (Sun CC, g ++ и MSC), не выводит никаких инструкций ограждения или мембраны. (Примерно в то время, когда Microsoft предлагала расширить правила
volatile
, я думаю, что некоторые из их компиляторов реализовали свое предложение и выдавали инструкции по ограничению для изменчивого доступа. Я не проверял, что делают последние компиляторы, но меня не удивит, если это зависит от на каком-то параметре компилятора. Версия, которую я проверял - я думаю, это была VS6.0 - однако, не создавала препятствий.)источник
volatile
семантика вполне адекватна. Обычно такие периферийные устройства сообщают, что их области памяти не кэшируются, что помогает при переупорядочении на аппаратном уровне.ASI_REAL_IO
часть адресного пространства, я думаю, с вами все будет в порядке. (Altera NIOS использует похожую технику с старшими битами адреса, управляющими обходом MMU; я уверен, что есть и другие)Это не обязательно. Volatile не является примитивом синхронизации. Он просто отключает оптимизацию, т.е. вы получаете предсказуемую последовательность чтения и записи в потоке в том же порядке, который предписан абстрактной машиной. Но чтение и запись в разных потоках изначально не имеют никакого порядка, нет смысла говорить о сохранении или не сохранении их порядка. Порядок между аддонами можно установить с помощью примитивов синхронизации, вы получите UB без них.
Небольшое объяснение относительно барьеров памяти. Типичный ЦП имеет несколько уровней доступа к памяти. Есть конвейер памяти, несколько уровней кеша, затем ОЗУ и т. Д.
Инструкции по мембране промыть трубопровод. Они не меняют порядок, в котором выполняются операции чтения и записи, а просто заставляют невыполненные операции выполняться в данный момент. Это полезно для многопоточных программ, но не намного.
Кэш (ы) обычно автоматически согласовываются между процессорами. Если кто-то хочет убедиться, что кеш синхронизирован с ОЗУ, необходима очистка кеша. Это очень отличается от мембраны.
источник
volatile
просто отключает оптимизацию компилятора? В этом нет никакого смысла. Любая оптимизация, которую может сделать компилятор, может, по крайней мере, в принципе, в равной степени выполняться процессором. Поэтому, если в стандарте говорится, что он просто отключил оптимизацию компилятора, это означало бы, что он не обеспечит поведения, на которое можно было бы положиться в переносимом коде. Но это, очевидно, не так, потому что переносимый код может полагаться на свое поведение в отношенииsetjmp
сигналов и.Компилятору необходимо
volatile
ограничить доступ к памяти тогда и только тогда, когда это необходимо для использованияvolatile
указанных в стандартной работе (setjmp
обработчиков сигналов и т. Д.) На этой конкретной платформе.Обратите внимание, что некоторые компиляторы действительно выходят за рамки того, что требуется стандартом C ++, чтобы сделать их
volatile
более мощными или полезными на этих платформах. Переносимый код неvolatile
должен делать ничего сверх того, что указано в стандарте C ++.источник
Я всегда использую volatile в подпрограммах обслуживания прерываний, например, ISR (часто ассемблерный код) изменяет некоторую ячейку памяти, а код более высокого уровня, который выполняется вне контекста прерывания, обращается к ячейке памяти через указатель на volatile.
Я делаю это для ОЗУ, а также для ввода-вывода с отображением памяти.
Основываясь на обсуждении здесь, кажется, что это все еще допустимое использование volatile, но не имеет ничего общего с несколькими потоками или процессорами. Если компилятор для микроконтроллера «знает», что других доступов быть не может (например, все на кристалле, нет кеша и есть только одно ядро), я бы подумал, что ограничение памяти вообще не подразумевается, компилятор просто нужно предотвратить определенные оптимизации.
По мере того, как мы накапливаем все больше вещей в «систему», которая выполняет объектный код, почти все ставки отключены, по крайней мере, так я прочитал это обсуждение. Как компилятор мог покрыть все основы?
источник
Я думаю, что путаница с изменяемым порядком и переупорядочением инструкций проистекает из двух понятий переупорядочивания, которые делают процессоры:
Volatile влияет на то, как компилятор генерирует код, предполагая однопоточное выполнение (включая прерывания). Это ничего не говорит о командах барьера памяти, но скорее препятствует компилятору выполнять определенные виды оптимизации, связанные с доступом к памяти.
Типичный пример - это повторная выборка значения из памяти вместо использования одного, кэшированного в регистре.
Внеочередное исполнение
ЦП могут выполнять инструкции не по порядку / предположительно при условии, что конечный результат мог быть получен в исходном коде. ЦП могут выполнять преобразования, которые запрещены в компиляторах, поскольку компиляторы могут выполнять только те преобразования, которые являются правильными во всех обстоятельствах. Напротив, процессоры могут проверить правильность этих оптимизаций и отказаться от них, если они окажутся неверными.
Последовательность чтения / записи памяти с точки зрения других процессоров
Конечный результат последовательности инструкций, эффективный порядок, должен соответствовать семантике кода, созданного компилятором. Однако фактический порядок выполнения, выбранный ЦП, может быть другим. Эффективный порядок, видимый в других процессорах (каждый процессор может иметь разное представление), может быть ограничен барьерами памяти.
Я не уверен, насколько эффективный и фактический порядок может отличаться, потому что я не знаю, в какой степени барьеры памяти могут препятствовать выполнению процессорами вне очереди.
Источники:
источник
Пока я работал над загружаемым онлайн-видеоуроком по разработке 3D-графики и игрового движка, работая с современным OpenGL. Мы использовали
volatile
в одном из наших классов. Веб-сайт с учебным курсом можно найти здесь, а видео о работе сvolatile
ключевым словом можно найти вShader Engine
серии видео 98. Эти работы не являются моими собственными, но аккредитованы,Marek A. Krzeminski, MASc
и это отрывок со страницы загрузки видео.И если вы подписаны на его веб-сайт и имеете доступ к его видео в этом видео, он ссылается на эту статью, касающуюся использования
Volatile
сmultithreading
программированием.Вот статья по ссылке выше: http://www.drdobbs.com/cpp/volatile-the-multithreaded-programmers-b/184403766
Эта статья может быть немного устаревшей, но она дает хорошее представление об отличном использовании модификатора volatile с использованием многопоточного программирования, чтобы помочь сохранить асинхронность событий, в то время как компилятор проверяет условия гонки за нас. Это может не напрямую отвечать на исходный вопрос OP о создании ограждения памяти, но я решил опубликовать это как ответ для других как отличную ссылку на хорошее использование volatile при работе с многопоточными приложениями.
источник
Ключевое слово по
volatile
существу означает, что чтение и запись объекта должны выполняться точно так, как написано программой, и никоим образом не оптимизироваться . Двоичный код должен соответствовать коду C или C ++: загрузка, где это читается, хранилище, где есть запись.Это также означает, что не следует ожидать, что чтение приведет к предсказуемому значению: компилятор не должен ничего предполагать о чтении даже сразу после записи в тот же изменчивый объект:
volatile int i; i = 1; int j = i; if (j == 1) // not assumed to be true
volatile
может быть самым важным инструментом в наборе инструментов «C - язык ассемблера высокого уровня» .Достаточно ли объявления объекта volatile для обеспечения поведения кода, который имеет дело с асинхронными изменениями, зависит от платформы: разные ЦП обеспечивают разные уровни гарантированной синхронизации для обычных операций чтения и записи в память. Вероятно, вам не стоит пытаться писать такой низкоуровневый многопоточный код, если вы не являетесь экспертом в этой области.
Атомарные примитивы обеспечивают хорошее представление объектов более высокого уровня для многопоточности, что упрощает рассуждения о коде. Почти все программисты должны использовать атомарные примитивы или примитивы, которые обеспечивают взаимные исключения, такие как мьютексы, блокировки чтения-записи, семафоры или другие блокирующие примитивы.
источник