Можно ли изменить порядок доступа к летучим веществам?

Рассмотрим следующую последовательность записей в volatile память, которую я взял из Статья Дэвида Чисналла в InformIT, «Понимание атомарности C11 и C ++ 11»:

volatile int a = 1;
volatile int b = 2;
a = 3;

Я понял из C ++ 98, что эти операции не могут быть переупорядочены, в соответствии с C ++ 98 1.9:

соответствующий
реализации должны эмулировать (только) наблюдаемое поведение абстрактной машины как
объяснено ниже

Наблюдаемое поведение абстрактной машины — это последовательность операций чтения и записи в изменчивые данные и
вызовы функций ввода / вывода библиотеки

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

a = 1;
a = 3;
b = 2;

Или это:

b = 2;
a = 1;
a = 3;

C ++ 11 повторяет формулировку C ++ 98, которая

соответствующий
реализации должны эмулировать (только) наблюдаемое поведение абстрактной машины, как объяснено
ниже.

но говорит об этом volatileс (1,9 / 8):

Доступ к изменчивым объектам оценивается строго по правилам абстрактной машины.

1.9 / 12 говорит, что доступ к volatile glvalue (который включает в себя переменные a, b, а также c выше) является побочным эффектом, и в 1.9 / 14 говорится, что побочные эффекты в одном полном выражении (например, в операторе) должны предшествовать побочным эффектам более позднего полного выражения в том же потоке. Это приводит меня к выводу, что два переупорядочения, которые показывает Чисналл, являются недействительными, поскольку они не соответствуют порядку, определяемому абстрактной машиной.

Я что-то упускаю или Чисналл ошибается?

(Обратите внимание, что это не многопоточный вопрос. Вопрос в том, разрешено ли компилятору изменять порядок доступа к различным volatile переменные в одном потоке.)

10

Решение

Интерпретация IMO Chisnalls (представленная вами) явно неверна. Более простой случай — C ++ 98. sequence of reads and writes to volatile data должен быть сохранен, и это относится к упорядоченной последовательности чтения и записи любых изменчивых данных, а не к одной переменной.

Это становится очевидным, если учесть исходную мотивацию для volatile: ввод-вывод с отображением в память. В mmio у вас обычно есть несколько связанных регистров в разных местах памяти, а протокол устройства ввода-вывода требует определенной последовательности чтения и записи в его набор регистров — порядок между регистрами важен.

Формулировка C ++ 11 избегает говорить об абсолютном sequence of reads and writesпотому что в многопоточных средах нет единой четко определенной последовательности таких событий между потоками — и это не проблема, если эти обращения осуществляются в независимые области памяти. Но я полагаю, что цель заключается в том, что для любой последовательности изменчивых обращений к данным с четко определенным порядком правила остаются такими же, как и для C ++ 98 — порядок должен быть сохранен, независимо от того, к какому количеству различных мест обращаются в этой последовательности.

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

Стандарт C ++ 11 оставляет место для гонок данных между несинхронизированными изменчивыми обращениями, поэтому нет ничего, что требовало бы окружения их полными заборами памяти или подобными конструкциями. Если есть части памяти, которые действительно используются в качестве внешнего интерфейса — для сопоставленных с памятью операций ввода-вывода или прямого доступа к памяти — тогда для реализации может быть разумным дать вам гарантии того, как энергозависимый доступ к этим частям подвергается потребляющим устройствам.

Одна гарантия, вероятно, может быть выведена из стандарта (см. [Into.execution]): значения типа volatile std::sigatomic_t должны иметь значения, совместимые с порядком записи в них даже в обработчике сигналов — по крайней мере, в однопоточной программе.

5

Другие решения

Ты прав, он не прав. Доступ к различным изменчивым переменным не может быть переупорядочен компилятором, если они происходят в отдельных полных выражениях, то есть разделены тем, что в C ++ 98 называется точкой последовательности, или в терминах C ++ 11 один доступ упорядочен раньше другого.

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

См. Правую нижнюю ячейку таблицы 1 в информативной Саттер изменчивый против изменчивый статья.

2

Похоже, это может случиться.

На этой странице есть обсуждение:

http://gcc.gnu.org/ml/gcc/2003-11/msg01419.html

0

Это зависит от вашего компилятора. Например, MSVC ++ с Visual Studio 2005 гарантирует, что * volatiles не будут переупорядочены (на самом деле, Microsoft отказалась и предполагала, что программисты будут постоянно злоупотреблять) volatile — MSVC ++ теперь добавляет барьер памяти для определенных случаев использования volatile). Другие версии и другие компиляторы могут не иметь таких гарантий.

Короче говоря: не ставьте на это. Правильно спроектируйте свой код и не используйте volatile. Вместо этого используйте барьеры памяти или полноценные мьютексы. C ++ 11-х atomic Типы помогут.

0

На данный момент, я собираюсь предположить, что ваш a=3Это просто ошибка при копировании и вставке, и вы действительно хотели, чтобы они были c=3,

Реальный вопрос здесь заключается в разнице между оценкой и тем, как вещи становятся видимыми для другого процессора. Стандарты описывают порядок оценки. С этой точки зрения, вы совершенно правы, учитывая a, b а также c в этом порядке назначения должны оцениваться в этом порядке.

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

Да, это вполне допустимо — процессор все еще оценивает назначения в точности в порядке, установленном стандартом, поэтому требования выполняются. Стандарт просто не предъявляет никаких требований к тому, что происходит после оценки, что и происходит здесь.

Я должен добавить: на некоторых аппаратных средствах это достаточно, хотя. Например, x86 использует отслеживание кэша, поэтому, если другой процессор пытается прочитать значение, которое было обновлено одним процессором (но все еще находится в кэше), процессор, имеющий текущее значение, задержит чтение другим процессор, пока текущее значение не может быть записано, так что другой процессор будет видеть текущее значение.

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

Изменить: если мы на мгновение игнорируем потоки, вопрос становится немного проще — но не намного. Согласно C ++ 11, §1.9 / 12:

Когда возвращается вызов библиотечной функции ввода-вывода или оценивается доступ к энергозависимому объекту, побочный эффект считается завершенным, даже если некоторые внешние действия, подразумеваемые вызовом (например, сам ввод-вывод) или энергозависимым доступом возможно, еще не завершено.

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

0

C ++ 98 не говорит, что инструкции нельзя переупорядочить.

Наблюдаемое поведение абстрактной машины — это последовательность операций чтения и записи в изменчивые данные и обращения к функциям ввода-вывода библиотеки.

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

Проще говоря, это ничего не значит. Не существует «единого правильного места» для наблюдения за порядком чтения и записи (шина ОЗУ? Шина ЦП? Между кэшами L1 и L2? Из другого потока? Из другого ядра?), Поэтому это требование по сути бессмысленно.

Версии C ++ до любых ссылок на потоки явно не определяют поведение изменчивых переменных, как видно из другого потока. И С ++ 11 (мудро, ИМО) не изменил это но вместо этого введены разумные атомарные операции с четко определенной семантикой между потоками.

Что касается отображенного в памяти оборудования, оно всегда будет зависеть от платформы. Стандарт C ++ даже не претендует на то, как это можно сделать правильно. Например, платформа может быть такой, что в этом контексте допустимы только подмножества операций с памятью, например, те, которые обходят буфер записи записи, который может переупорядочиваться, и стандарт C ++, конечно, не заставляет компилятор выдавать правильные инструкции для это конкретное аппаратное устройство — как оно могло?

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

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

Поскольку никто не верит, что стандарт требует, чтобы компилятор выдавал инструкции, чтобы удерживать ЦП от переупорядочения доступа к изменчивым переменным, а современные компиляторы этого не делают, никто не должен полагать, что стандарт C ++ запрещает компилятору переупорядочивать доступы к разным переменным.

-2
По вопросам рекламы [email protected]