Фраза «сильно случается раньше» используется несколько раз в проекте стандарта C ++.
Например: Завершение [basic.start.term] / 5
Если завершение инициализации объекта со статической продолжительностью хранения сильно происходит до вызова std :: atexit (см. [Support.start.term]), вызов функции передается в std :: atexit последовательность перед вызовом деструктора для объекта. Если вызов std :: atexit сильно происходит до завершения инициализации объекта со статической продолжительностью хранения, то вызов деструктора для объекта упорядочивается до того, как вызов функции передается в std :: atexit. , Если вызов std :: atexit сильно происходит перед другим вызовом std :: atexit, то вызов функции, переданной второму вызову std :: atexit, упорядочивается до вызова функции, переданной первый вызов std :: atexit.
И определено в гонках данных [intro.races] / 12
Оценка A сильно случается перед оценкой D, если либо
(12.1) A секвенируется перед D, или
(12.2) A синхронизируется с D, и A и D являются последовательно согласованными атомарными операциями ([atomics.order]), или
(12.3) существуют оценки B и C такие, что A секвенируется перед B, B просто происходит перед C, а C секвенируется перед D, или
(12.4) существует оценка B такая, что A сильно случается до B, а B сильно предшествует D.
[Примечание: неофициально, если A сильно случается до B, то A, кажется, оценивается перед B во всех контекстах. Сильно случается до того, как исключаются операции потребления. - конец примечания]
Почему было «сильно случается прежде» введено? Интуитивно понятно, в чем его отличие и связь с «случается раньше»?
Что означает «А, по-видимому, оценивается перед В во всех контекстах» в примечании?
(Примечание: мотивация для этого вопроса - комментарии Питера Кордеса под этим ответом .)
Дополнительный проект стандартной цитаты (спасибо Peter Cordes)
Порядок и последовательность [atomics.order] / 4
Для всех операций memory_order :: seq_cst, включая заборы, существует один общий порядок S, который удовлетворяет следующим ограничениям. Во-первых, если A и B являются операциями memory_order :: seq_cst и A сильно предшествует B, то A предшествует B в S. Во-вторых, для каждой пары атомарных операций A и B над объектом M, где A упорядочено по когерентности перед B следующие S должны выполнить следующие четыре условия:
(4.1) если A и B являются обеими операциями memory_order :: seq_cst, то A предшествует B в S; а также
(4.2) если A является операцией memory_order :: seq_cst и B происходит перед забором памяти memory_order :: seq_cst Y, то A предшествует Y в S; а также
(4.3) если ограничение memory_order :: seq_cst X происходит до того, как A и B является операцией memory_order :: seq_cst, то X предшествует B в S; а также
(4.4) если ограничение memory_order :: seq_cst X происходит до A и B происходит до забора memory_order :: seq_cst Y, то X предшествует Y в S.
seq_cst
в Atomics 31.4 Порядок и последовательность: 4 . Этого нет в стандарте C ++ 17 n4659 , где 32.4-3 определяют существование единого общего порядка операций seq_cst, соответствующих порядку «происходит раньше» и порядкам модификации для всех затронутых местоположений ; «сильно» был добавлен в более поздний проект.atexit()
в одном потоке иexit()
в другом, инициализаторам недостаточно переносить только зависимость, основанную на потреблении, только потому, что результаты тогда отличаются от того,exit()
был ли вызван тем же потоком. Мой более старый ответ касался этой разницы.exit()
. Любой поток может убить всю программу, выйдя из системы, или основной поток может выйти,return
-ing. Это приводит к вызовуatexit()
обработчиков и смерти всех потоков, что бы они ни делали.Ответы:
Готовьтесь к тому, что «просто случается раньше»! Посмотрите на этот текущий снимок cppref https://en.cppreference.com/w/cpp/atomic/memory_order
Кажется, «просто случается раньше» добавлено в C ++ 20.
Так что Simply-HB и HB одинаковы, за исключением того, как они обрабатывают операции потребления. Смотрите HB
Как они отличаются в отношении потребления? Смотрите Inter-Thread-HB
Операция, которая упорядочена по зависимости (то есть использует выпуск / потребление), является HB, но не обязательно просто-HB.
Потребление более расслаблено, чем приобретение, поэтому, если я правильно понимаю, HB более расслаблен, чем Simply-HB.
Таким образом, операция освобождения / потребления не может быть строго-HB.
Освобождение / приобретение может быть HB и Simply-HB (потому что релиз / приобретение синхронизируется с), но не обязательно строго-HB. Потому что Strongly-HB, в частности, говорит, что A должен синхронизироваться с B, и быть последовательной последовательной операцией.
Все контексты: все потоки / все процессоры видят (или «в конечном итоге согласятся») один и тот же порядок. Это является гарантией последовательной согласованности - глобального общего порядка изменения всех переменных. Цепочки получения / освобождения гарантируют только предполагаемый порядок модификации для потоков, участвующих в цепочке. Нити вне цепочки теоретически могут видеть другой порядок.
Я не знаю, почему были введены Strongly-HB и Simply-HB. Может быть, чтобы помочь уточнить, как работать вокруг потреблять? Сильно-HB обладает хорошими свойствами - если один поток наблюдает за A-случаем-до-B, он знает, что все потоки будут наблюдать одно и то же.
История потребления:
Пол Э. МакКенни отвечает за то, чтобы потребители были в стандартах C и C ++. Потребление гарантирует порядок между назначением указателя и памятью, на которую он указывает. Это было изобретено из-за DEC Alpha. DEC Alpha может умело разыменовывать указатель, поэтому он также имеет забор памяти для предотвращения этого. DEC Alpha больше не производится, и ни у одного из современных процессоров такого поведения нет. Потребление предназначено, чтобы быть очень расслабленным.
источник
mo_consume
предназначена для использования преимуществ упорядочения зависимости данных на реальных процессорах и формализации того, что компилятор не может нарушить зависимость данных с помощью прогнозирования ветвлений. Например,int *p = load();
tmp = *p;
может быть нарушен введением компилятора,if(p==known_address) tmp = *known_address; else tmp=*p;
если у него есть основания ожидать, что определенное значение указателя будет общим. Это законно для расслабленного, но не потреблять.