Будут ли две расслабленные записи в одно и то же место в разных потоках всегда рассматриваться в одном и том же порядке другими потоками?
В архитектуре x86 хранилища в одной и той же ячейке памяти имеют общий порядок, например, смотрите это видео. Каковы гарантии в модели памяти C++11?
Точнее, в
-- Initially --
std::atomic<int> x{0};
-- Thread 1 --
x.store(1, std::memory_order_release);
-- Thread 2 --
x.store(2, std::memory_order_release);
-- Thread 3 --
int r1 = x.load(std::memory_order_acquire);
int r2 = x.load(std::memory_order_acquire);
-- Thread 4 --
int r3 = x.load(std::memory_order_acquire);
int r4 = x.load(std::memory_order_acquire);
будет ли результат r1==1, r2==2, r3==2, r4==1
быть разрешено (на некоторых архитектурах, кроме x86)? Что, если я должен был заменить все memory_order
это std::memory_order_relaxed
?
3 ответа
Нет, такой исход не допускается. §1.10 [intro.multithread]/p8, 18 (цитирование N3936/C++14; тот же текст можно найти в пунктах 6 и 16 для N3337 / C++ 11):
8 Все модификации конкретного атомного объекта M происходят в определенном общем порядке, называемом порядком модификации M.
18 Если вычисление значения A атомарного объекта M происходит до вычисления значения B из M, и A берет свое значение из побочного эффекта X на M, то значение, вычисленное с помощью B, должно быть либо значением, сохраненным X, либо значением сохраняется побочным эффектом Y на M, где Y следует за X в порядке модификации M. [ Примечание: это требование известно как согласованность чтения-чтения. -конец примечания ]
В вашем коде есть два побочных эффекта, и к p8 они проявляются в каком-то определенном общем порядке. В потоке 3 вычисление значения для вычисления значения, которое будет сохранено в r1
происходит до того из r2
так дано r1 == 1
а также r2 == 2
мы знаем, что хранилище, выполняемое потоком 1, предшествует хранилищу, выполняемому потоком 2, в порядке изменения x
, В таком случае, Thread 4
не может наблюдать r3 == 2, r4 == 1
без столкновения с p18. Это независимо от memory_order
используемый.
В p21 есть примечание (p19 в N3337), которое имеет отношение:
[ Примечание: четыре предыдущих требования согласованности эффективно запрещают переупорядочивание компилятором атомарных операций для одного объекта, даже если обе операции являются ослабленными нагрузками. Это эффективно обеспечивает гарантию согласованности кэша, предоставляемую большинством аппаратного обеспечения, доступного для атомарных операций C++ -конец примечания ]
Согласно C++11 [intro.multithread]/6: "Все модификации определенного атомарного объекта M
происходят в каком-то конкретном общем порядке, называемом порядком модификации M
Msgstr "Следовательно, при чтении атомарного объекта конкретным потоком никогда не будут видны" более старые "значения, чем те, которые поток уже наблюдал. Обратите внимание, что здесь нет упоминаний об упорядочениях памяти, поэтому это свойство верно для всех из них - seq_cst
через relaxed
,
В примере, приведенном в OP, порядок модификации x
может быть (0,1,2)
или же (0,2,1)
, Поток, который обнаружил заданное значение в этом порядке изменения, не может позднее наблюдать более раннее значение. Исход r1==1, r2==2
подразумевает, что порядок модификации x
является (0,1,2)
, но r3==2, r4==1
подразумевает, что это (0,2,1)
противоречие. Так что такой результат невозможен для реализации, соответствующей C++11 .
Учитывая, что правила C++11 определенно запрещают это, вот более качественный / интуитивный способ понять это:
Если больше нет магазинов в x
В конце концов все читатели согласятся с его ценностью. (т.е. один из двух магазинов вышел вторым).
Если бы разные потоки могли расходиться во мнениях относительно порядка, то либо они постоянно / в течение длительного времени не соглашались бы со значением, либо один поток мог видеть изменение значения в 3-й дополнительный раз (фантомное хранилище).
К счастью, C++11 не позволяет ни одну из этих возможностей.