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

В этом руководстве говорится следующее:

каждая загрузка на x86/64 уже подразумевает семантику получения, а каждое сохранение подразумевает семантику выпуска.

Теперь скажем, у меня есть следующий код (я написал свои вопросы в комментариях):

/* Global Variables */

int flag = 0;
int number1;
int number2;

//------------------------------------

/* Thread A */

number1 = 12345;
number2 = 678910;
flag = 1; /* This is a "store", so can I not use a release barrier here? */

//------------------------------------

/* Thread B */

while (flag == 0) {} /* This is a "load", so can I not use an acquire barrier here? */
printf("%d", number1);
printf("%d", number2);

person James    schedule 22.05.2018    source источник
comment
Вам и раньше помогали прилично, непонятно, почему вы упорно пытаетесь сделать это неправильно.   -  person Hans Passant    schedule 22.05.2018
comment
@ Ханс Пассант Я не хочу делать это так, я просто пытаюсь узнать об этом (упорядочение памяти, барьеры памяти и т. Д.).   -  person James    schedule 22.05.2018
comment
У вас все еще есть проблемы, связанные с компилятором, которые необходимо учитывать: 1- Убедитесь, что компилятор размещает переменные из памяти и никогда не кэширует их в регистрах. 2- Обеспечение атомарности конкретных доступов к переменным. 3- Обеспечение того, чтобы компилятор не переупорядочивал доступы. Все эти проблемы связаны с языком и компилятором; они не вызваны x86-64 напрямую. Я могу вернуться через некоторое время и написать ответ, если никто не ответил.   -  person Hadi Brais    schedule 23.05.2018


Ответы (2)


В руководстве рассказывается об загрузках и хранениях на уровне сборки/машины, где вы можете полагаться на семантику x86 ISA, включающую получение при загрузке и выпуск при загрузке. хранить.

Ваш код, однако, C, который вообще не дает таких гарантий, и компилятор может преобразовать его во что-то совершенно отличное от того, что вы ожидаете с точки зрения загрузки и хранения. Это не теоретически, это происходит на практике. Итак, краткий ответ: нет, это невозможно сделать легально и переносимо на C, хотя это может сработать, если вам повезет.

person BeeOnRope    schedule 23.05.2018
comment
Допустим, я использовал volatile для глобальных переменных (таким образом компилятор будет хранить глобальные переменные в памяти, а не оптимизировать их в регистрах), и допустим, что я также использовал барьеры компилятора, чтобы предотвратить переупорядочение компилятора. Будет ли мой код теперь работать так, как я ожидал? - person James; 23.05.2018
comment
Конечно, это может снова сработать по счастливой случайности (с возрастающей вероятностью), но volatile не связан с многопоточным доступом в модели памяти C11, поэтому вы все равно окажетесь в стране неопределенного поведения. Стоит отметить, что до модели памяти C11 стандарт просто молчал по этому вопросу, поэтому единственный способ сделать что-то был с volatile (иногда, часто вам это не нужно, так как непрозрачный вызов функции имеет желаемый эффект на компилятор) и барьеры непереносимой памяти (в дополнение к непереносимым атомарным операциям, конечно). - person BeeOnRope; 23.05.2018
comment
Возможно, вам лучше задать этот вопрос на ассемблере или удалить тег C и объяснить, что это просто C-подобный псевдокод, который компилируется построчно до того, что вы ожидаете на уровне сборки, если вы не не знаю или не хочу писать пример на ассемблере. @Джеймс - person BeeOnRope; 23.05.2018
comment
Я читал, что C11 знает о потоках, поэтому вы можете написать многопоточную программу на C11 и скомпилировать ее на любой архитектуре ЦП, которую вы хотите, и поведение программы останется прежним. Но допустим, что я использую стандарт C до C11 и хочу написать многопоточную программу, в этом случае я должен думать с точки зрения модели памяти архитектуры ЦП, на которой работает компилятор, верно? Теперь предположим, что я скомпилировал свой код под x86 с помощью C99. Разве мой код теперь не будет работать так, как я ожидал (поскольку x86 гарантирует получение при загрузке и выпуск при хранении)? - person James; 23.05.2018
comment
@James: Нет, потому что вы все еще пишете это на C, поэтому модель слабой памяти C применяется для переупорядочения во время компиляции. (Или несуществующая/де-факто модель памяти, если вы компилируете как C99.) переупорядочивание GCC инструкций чтения/записи / Есть ли какой-либо барьер компилятора, равный asm(:::memory) в C++11? - person Peter Cordes; 23.05.2018
comment
@Peter Cordes Что, если бы я использовал барьеры компилятора, мой код теперь работал бы так, как я ожидал (я все еще говорю о компиляции с использованием C99 под x86). - person James; 23.05.2018
comment
@James: да, барьеры (или встроенные функции компилятора, такие как __sync_compare_and_swap) - это то, как выполнялся код без блокировок до C11, еще в старые недобрые времена, когда компиляторы понимали атомарные операции и позволяли вам точно сказать им, что вы хотели, используя C11 стандартный. - person Peter Cordes; 23.05.2018

Предположим, что код написан на 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
comment
если есть другие обращения к энергонезависимой памяти, которые выше или ниже хранилища, чтобы пометить, то такие обращения все равно могут быть переупорядочены Но если есть другие обращения к энергонезависимой памяти, которые выше сохранения, чтобы flag , они не будут переупорядочены ниже магазина до flag, так как магазин подразумевает семантику выпуска в x86, правильно? - person James; 23.05.2018
comment
@James Да, на уровне машинного кода x86. Но это код C, так что нет. - person Hadi Brais; 23.05.2018
comment
Вы правы, я перечитал ваш ответ, я не обратил внимания, когда впервые прочитал его, что вы говорили о том, что компилятор выполняет переупорядочение. - person James; 23.05.2018
comment
Что касается вашего абзаца об атомарности, вы говорите, что хранилище для int (32-битного размера) под x86 не является атомарным, и что если его читает другой поток, он может прочитать только некоторые измененные биты ? потому что я думал, что только long long (имеющий 64-битный размер) не является атомарным под x86 (потому что для него требуются две инструкции сохранения). - person James; 23.05.2018
comment
@James Но int может быть не 32-битного размера. Это зависит от компилятора. Кроме того, его выравнивание также зависит от компилятора. Если используемый компилятор реализует int как 32-битный и если он содержится в строке кэша (не пересекая границу строки кэша), то x86-64 гарантирует атомарность. Проблема действительно на уровне языка, а не x86. - person Hadi Brais; 23.05.2018
comment
Вы говорите Обратите внимание, что volatile не указывает компилятору, что хранилище должно быть немедленно глобально видимым для других потоков. На х86 так и есть. C на самом деле не предназначен для машин, которым требуется явная когерентность, потому что нет механизмов, позволяющих указать, какие более ранние неатомарные хранилища необходимо сделать глобально видимыми. Все, что сильнее расслабления или потребления, всегда требует повторной синхронизации всего с глобальным состоянием. (Думаю, если оптимизация всей программы с поддержкой многопоточности не доказала, что некоторые вещи не нужно синхронизировать.) - person Peter Cordes; 12.07.2018
comment
Или акцент был сделан на немедленном, а не на барьере, который заставляет этот поток ждать, пока более ранние хранилища станут видимыми? - person Peter Cordes; 12.07.2018
comment
@PeterCordes Да. В частности, volatile не заставляет компилятор выдавать sfence после каждой записи в volatile-переменную. - person Hadi Brais; 12.07.2018
comment
@HadiBrais: ну и что? x86 не нуждается в sfence для семантики выпуска, если только вы не использовали хранилища NT. (И тогда он понадобится вам перед магазином в volatile). Со всеми тремя переменными volatile это будет работать на x86. (Поскольку компилятор не может изменить порядок доступа к объектам volatile, а аппаратное обеспечение обеспечивает достаточный порядок выполнения для acq/rel). - person Peter Cordes; 12.07.2018
comment
Что мешает компилятору разместить его в регистре на все время жизни программы? Хороший вопрос. Если компилятор не видит код для всех функций (например, printf), он не может доказать отсутствие мьютексов или атомарных операций acq/rel, требуя, чтобы другие потоки видели значение этого потока. присвоен глобальный. Поэтому я думаю, что наличие каких-либо библиотечных (или других не встроенных) вызовов функций - это то, что препятствует оптимизации всей программы для глобального преобразования в регистр. - person Peter Cordes; 12.07.2018
comment
@PeterCordes Да, я отредактировал ответ, просто чтобы сказать, что sfence не требуется для семантики выпуска. Что касается распределения регистров, имеет смысл. - person Hadi Brais; 12.07.2018
comment
Сразу видимая формулировка все еще сбивает с толку. Если вы не говорите о системе явной согласованности (в отличие от обычных процессоров), то это звучит как Если я не использую заборы, как долго может ли ядро ​​увидеть записи другого ядра? где вы подразумеваете, что барьеры ускоряют глобальную видимость, вместо того, чтобы фактически заставлять текущее ядро ​​​​/поток ждать. Сохраняющий поток не выполняет никаких последующих загрузок, поэтому даже seq-cst (который заставит текущий поток ждать) не будет иметь значения. - person Peter Cordes; 12.07.2018
comment
@PeterCordes Я удалил все предложение. - person Hadi Brais; 12.07.2018