C ++ std :: memory_order_relaxed путаница
Я читал эту статью из GCC Wiki о барьерах памяти C ++ (и это круто).
Пока я не добрался до этого момента, это было довольно просто:
Противоположный подход - std :: memory_order_relaxed. Эта модель допускает гораздо меньшую синхронизацию за счет устранения ограничений, связанных с тем, что происходит раньше. Эти типы атомарных операций также могут иметь различные оптимизации, такие как удаление мертвых хранилищ и объединение. Итак, в предыдущем примере:
-Thread 1-
y.store (20, memory_order_relaxed)
x.store (10, memory_order_relaxed)
-Thread 2-
if (x.load (memory_order_relaxed) == 10)
{
assert (y.load(memory_order_relaxed) == 20) /* assert A */
y.store (10, memory_order_relaxed)
}
-Thread 3-
if (y.load (memory_order_relaxed) == 10)
assert (x.load(memory_order_relaxed) == 10) /* assert B */
Поскольку потоки не нужно синхронизировать в системе, любой из assert в этом примере может на самом деле FAIL.
Хорошо, это тоже просто, давайте продолжим ...
-Thread 1-
x.store (1, memory_order_relaxed)
x.store (2, memory_order_relaxed)
-Thread 2-
y = x.load (memory_order_relaxed)
z = x.load (memory_order_relaxed)
assert (y <= z)
Утверждение не может потерпеть неудачу. Как только поток 2 видит хранилище 2, он больше не может видеть значение 1. Это предотвращает объединение ослабленных нагрузок одной переменной с ослабленными нагрузками другой ссылки, которая может быть псевдонимом.
Вот что меня смутило: почему y не может загрузить значение 2, а z загрузить значение 1 (и привести к сбою утверждения), поскольку порядок не синхронизирован в потоке 1?
Ответы
Ослабленное упорядочение относится к упорядочиванию операций по отношению к другим доступам к памяти , а не к упорядочиванию относительно релакс-модифицированного атома. В вашем первом случае тот факт, что вы видите 10 в xдюймах, ничего не значит в отношении значения y. И наоборот.
Но ваш второй случай отличается, потому что он влияет на тот же атомарный объект.
[intro.races] / 10 сообщает нам, что в потоке, если одна операция выполняется раньше другой, то эта операция «происходит раньше» другой. И [intro.races] / 14-17 описывают следующее поведение в отношении атомики :
Четыре предшествующих требования согласованности эффективно запрещают компилятор переупорядочивать атомарные операции для одного объекта, даже если обе операции являются нерегулируемыми нагрузками.
И вот что у вас есть. Все изменения происходят с одним и тем же объектом, поэтому они должны происходить в определенном порядке. Даже если этот порядок не может быть определен точно, он должен соответствовать отношениям кода «происходит раньше».
Две операции потока 1 упорядочены отношением «происходит раньше». И операции потока 2 сами упорядочиваются отношением «происходит раньше».
Поскольку все они действуют на один и тот же атомарный объект, если он yполучает значение 2, значит, "произошло после" xбыло установлено значение 2. Таким образом, порядок доступа для xдолжен был быть "x = 1, x = 2, прочтите x ". И поскольку последнее чтение xпроисходит после первого чтения x, полученное значение не может быть 1.