Будут ли две непринужденные записи в одно и то же место в разных потоках всегда рассматриваться в одном и том же порядке другими потоками?

В архитектуре 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?

13

Решение

Нет, такой исход не допускается. §1.10 [intro.multithread] / p8, 18 (цитирование N3936 / C ++ 14; тот же текст можно найти в пунктах 6 и 16 для N3337 / C ++ 11):

8 Все модификации конкретного атомного объекта М происходят в некоторых
конкретный общий порядок, называемый порядок модификации М.

18 Если вычисление значения A атомного объекта M происходит до
вычисление значения B из M, и A берет свое значение из побочного эффекта X
на M, то значение, вычисленное с помощью B, должно быть либо значением, сохраненным
X или значение, сохраненное побочным эффектом Y на M, где Y следует за X в
порядок модификации М. [ Заметка: Это требование известно как
согласованность чтения-чтения. —конечная нота ]

В вашем коде есть два побочных эффекта, и к p8 они проявляются в каком-то определенном общем порядке. В потоке 3 вычисление значения для вычисления значения, которое будет сохранено в r1 происходит до того из r2так дано r1 == 1 а также r2 == 2 мы знаем, что хранилище, выполняемое потоком 1, предшествует хранилищу, выполняемому потоком 2, в порядке изменения x, В таком случае, Thread 4 не может наблюдать r3 == 2, r4 == 1 без столкновения с p18. Это независимо от memory_order используемый.

В p21 есть примечание (p19 в N3337), которое имеет отношение:

[ Заметка: Четыре предыдущих требования согласованности эффективно
не разрешать компилятору переупорядочивать атомарные операции для одного объекта,
даже если обе операции являются расслабленными нагрузками. Это эффективно делает
гарантия согласованности кэша, предоставляемая большинством оборудования, доступного для C ++
атомные операции. —конечная нота ]
9

Другие решения

Согласно C ++ 11 [intro.multithread] / 6: «Все модификации определенного атомарного объекта M происходят в каком-то конкретном общем порядке, называемом порядком модификации MMsgstr «Следовательно, при чтении атомарного объекта конкретным потоком никогда не будут видны» более старые «значения, чем те, которые поток уже наблюдал. Обратите внимание, что здесь нет упоминаний об упорядочениях памяти, поэтому это свойство верно для всех из них — 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.

5

Учитывая, что правила C ++ 11 определенно запрещают это, вот более качественный / интуитивный способ понять это:

Если больше нет магазинов в xВ конце концов все читатели согласятся с его ценностью. (то есть один из двух магазинов пришел вторым).

Если бы разные потоки могли расходиться во мнениях относительно порядка, то либо они постоянно / в течение длительного времени не соглашались бы со значением, либо один поток мог видеть изменение значения в 3-й дополнительный раз (фантомное хранилище).

К счастью, C ++ 11 не позволяет ни одну из этих возможностей.

0
По вопросам рекламы [email protected]