Предположим, у нас есть одна простая переменная (std::atomic<int> var
) и 2 темы T1
а также T2
и у нас есть следующий код для T1
:
...
var.store(2, mem_order);
...
и для T2
...
var.load(mem_order)
...
Также давайте предположим, что T2
(нагрузка) выполняет 123 нс позже во времени(позже в порядке изменения в терминах стандарта C ++), чем T1
(хранить).
Мое понимание этой ситуации заключается в следующем (для разных порядков памяти):
memory_order_seq_cst
— T2
груз обязан загрузить 2
, Так эффективно он должен загружать последнее значение (как в случае с операциями RMW)memory_order_acquire
/memory_order_release
/memory_order_relaxed
— T2
не обязан загружать 2
но может загружать любое старое значение с единственным ограничением: это значение не должно быть старше, чем последнее, загруженное этим потоком. Так, например var.load
возвращается 0
,Я прав с моим пониманием?
Update1:
Если я ошибаюсь в рассуждениях, предоставьте текст из стандарта C ++, который подтверждает это. Не только теоретическое обоснование того, как может работать какая-то архитектура.
Я прав с моим пониманием?
Нет. Вы неправильно понимаете заказы памяти.
давайте предположим, что
T2
(загрузка) выполняет на 123 нс позже, чемT1
(хранить)…
В этом случае T2 увидит, что T1 делает с любым типом заказов памяти (более того, это свойство применяется для чтения / записи любой области памяти, см., Например, http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4431.pdf, 1.10, с.15). Ключевое слово в вашей фразе потом: это означает, что кто-нибудь другой заставляет упорядочивать эти операции.
Заказы памяти используются для другого сценария:
Позволяет некоторую операцию OP1
приходит в потоке T1
перед операцией магазина, OP2
приходит после этого, OP3
приходит в потоке T2
перед нагрузкой, OP4
приходит после этого.
//T1: //T2:
OP1 OP3
var.store(2, mem_order) var.load(mem_order)
OP2 OP4
Предположим, что некоторый порядок между var.store()
а также var.load()
можно наблюдать по темам. Что можно гарантировать порядок перекрестных потоков других операций?
var.store
использования memory_order_release
, var.load
использования memory_order_acquire
а также var.store
заказан до var.load
(то есть нагрузка возвращает 2), затем эффект OP1
заказан до OP4
,Например, если OP1
пишет некоторую переменную var1, OP4
читает эту переменную, то можно быть уверенным, что OP4
прочтем что OP1
пиши раньше. Это наиболее используемый случай.
var.store
а также var.load
использования memory_order_seq_cst
а также var.store
заказан после var.load
(то есть load возвращает 0, которое было значением переменной до сохранения), затем эффект OP2
заказан после OP3
,Этот порядок памяти требуется некоторыми хитрыми схемами синхронизации.
var.store
или же var.load
использования memory_order_relaxed
то с любым порядком var.store
а также var.load
можно гарантировать нет заказа операций перекрестных потоков.Этот порядок памяти используется в случае, когда кто-нибудь другой обеспечить порядок операций. Например, если нить T2
творение приходит после var.store
в T1
, затем OP3
а также OP4
заказаны после OP1
,
ОБНОВИТЬ: 123 ns later
подразумевает *someone else* force ordering
потому что процессор компьютера не имеет понятия об универсальном времени, и ни одна операция не имеет точный момент когда это будет выполнено. Для измерения времени между двумя операциями вы должны:
Переходно, эти шаги делают упорядочивание между первой операцией и второй.
Не найдя аргументов, доказывающих, что мое понимание неверно, я считаю его правильным, и мое доказательство состоит в следующем:
memory_order_seq_cst — загрузка T2 обязана загрузить 2.
Это правильно, потому что все операции, использующие memory_order_seq_cst
должен формировать единый общий порядок по атомарной переменной всех операций с памятью.
Выдержка из стандарта:
[29.9 / 3] На всех элементах memory_order_seq_cst должен быть один общий порядок S
операции, соответствующие порядку «происходит раньше» и
заказы на изменение для всех затронутых местоположений, так что каждый
Операция memory_order_seq_cst B, которая загружает значение из атомарного
объект М наблюдает одно из следующих значений <…>
Следующий пункт моего вопроса:
memory_order_acquire / memory_order_release / memory_order_relaxed — T2 — это
не обязан загружать 2, но может загрузить любое старое значение <…>
Я не нашел никаких доказательств, которые могли бы указывать, что нагрузка, выполненная позже в порядке модификации, должна видеть последнее значение. Единственные точки, которые я нашел для операций сохранения / загрузки с любым порядком памяти, отличным от memory_order_seq_cst
эти:
[29.3 / 12] Реализации должны сделать атомарные хранилища видимыми для атомарных
загружается в течение разумного периода времени.
а также
[1.10 / 28] Реализация должна гарантировать, что последнее значение (в порядке изменения) назначено атомарной операцией или операцией синхронизации
станет видимым для всех других потоков за конечный период времени.
Таким образом, единственная гарантия, которую мы имеем, состоит в том, что записанная переменная будет видна в течение некоторого времени — это довольно разумная гарантия, но она не подразумевает немедленной видимости предыдущего хранилища. И это подтверждает мою вторую мысль.
Учитывая все это, мое первоначальное понимание было правильным.