Предположим, что код написан на C99, а целевая архитектура — x86. Сильная модель памяти x86 действует только на уровне машинного кода. C99 не имеет модели памяти. Я объясню, что может пойти не так, и обсужу, существует ли способ решения проблем, совместимый с C99.
Во-первых, мы должны убедиться, что ни одна из переменных не оптимизирована и что все обращения к flag
, number1
и number2
происходят из памяти, а не кэшируются в регистрах процессора1. Этого можно добиться в C99, уточнив все три переменные с помощью volatile
.
Во-вторых, мы должны убедиться, что у сохранения в flag
в первом потоке есть семантика освобождения. Эта семантика включает две гарантии: сохранение в flag
не переупорядочивается при предыдущих обращениях к памяти и делает хранилище видимым для второго потока. Ключевое слово volatile
сообщает компилятору, что доступ к переменной может иметь наблюдаемые побочные эффекты. Это не позволяет компилятору переупорядочивать доступ к изменчивым переменным по отношению к другим операциям, которые также рассматриваются компилятором как имеющие наблюдаемые побочные эффекты. То есть, сделав все три переменные volatile
, компилятор сохранит порядок всех трех хранилищ в первом потоке. Тем не менее, если есть другие обращения к энергонезависимой памяти, которые находятся выше или ниже хранилища до flag
, то такие обращения все еще могут быть переупорядочены. Таким образом, стандарт volatile
обеспечивает только частичную семантику выпуска.
В-третьих... на самом деле для вашего конкретного фрагмента кода атомарность не требуется. Это потому, что сохранение в flag
изменяет только один бит, который по своей сути является атомарным. Так что для этого конкретного кода вам не нужно беспокоиться об атомарности. Но в целом, если хранилище на flag
может измениться более чем на один бит и если условие, проверенное во втором потоке, может вести себя по-разному в зависимости от того, видит ли он все или некоторые из битовых изменений, тогда вам, безусловно, нужно убедиться, что доступ to 'flag' являются атомарными. К сожалению, C99 не имеет понятия атомарности.
Чтобы получить полную семантику и атомарность выпуска, вы можете либо использовать атомарность C11 (как обсуждалось в цитируемой вами статье), либо прибегнуть к методам, специфичным для компилятора (также обсуждаемым в цитируемой вами статье). Конечно, можно еще просто посмотреть на сгенерированный машинный код и посмотреть, предлагает ли сама модель памяти x86 необходимые требования к корректности. Это невозможно на больших кодовых базах. Кроме того, при следующей компиляции кода сгенерированный машинный код может измениться. Наконец, поскольку вы всего лишь человек, вы можете совершить ошибку.
(1) В цитируемой статье переменная A
объявлена как общая глобальная переменная. Теперь, скорее всего, компилятор выделит его из памяти. Но соответствует ли это строгому стандарту? Что мешает компилятору разместить его в регистре на все время жизни программы? Не уверен в этом.
person
Hadi Brais
schedule
23.05.2018
flag.store(1, std::memory_order_release)
(или эквивалент стандартного атома C11) будет достаточно (если вы использовалиatomic_int flag
). Связанный: preshing.com/20131125/ а>. С флагом в виде простогоint
, а неatomic_int
, никакие барьеры не могут сделать этот C11 законным, потому что вы читаете его, пока он пишется другим потоком. Устаревший код до C11 поместил бы какой-то барьер памяти внутри цикла в качестве хака, чтобы заставить компилятор перезагрузить значение из памяти даже для простогоint
. - person Peter Cordes   schedule 23.05.2018