memory_order
не имеет видимого эффекта в контексте одного потока:
Давайте посмотрим (x
, a
и b
изначально 0
):
auto t1(std::atomic<int>& x, int& a, int& b)
{
a = 3; // 1
b = 5; // 2
x.store(a, std::memory_order_relaxed); // 3
}
Поскольку (1) и (2) не зависят друг от друга, компилятор может изменить их порядок.Например, можно выполнить (1) -> (2) или (2) -> (1)
Поскольку (3) зависит от (1) ((1) записи в a
и (3) чтения изa
) компилятор не может выполнить (3) до (1).Это независимо от того, какой порядок памяти указан в (3)
Поскольку (3) не зависит от (2), как правило, в однопоточной модели, компилятор может выполнить (3) перед (2).
Но поскольку x
является атомарным, рассмотрим другой поток, делающий это (x
, a
и b
являются ссылками на те же аргументы, что и для t1
, и все они изначально 0
):
auto t2(std::atomic<int>& x, int& a, int& b)
{
while(x.load(std::memory_order_relaxed) == 3) // 4
assert(b == 5); // 5
}
Этот поток ожидает, пока x
не станет 3
, а затем подтвердит, что b
равен 5
.Теперь вы можете видеть, как в последовательном однопоточном мире (2) и (3) можно изменить порядок без какого-либо наблюдаемого поведения, но в многопоточной модели порядок (2) и (3) может оказать влияние на поведениепрограммы.
Это то, что делает memory_order
: оно указывает, могут ли операции, которые могли быть переупорядочены до или после атома без какого-либо влияния на один поток, быть переупорядочены как таковые или нет.Причина в том, что они могут влиять на многопоточные программы.Компилятор не может знать это, только программист, следовательно, дополнительный параметр memory_order
.
При memory_order_relaxed
может произойти сбой подтверждения, потому что (2) может произойти после (3), но с memory_order_seq_cst
(по умолчанию)) утверждение никогда не потерпит неудачу, потому что (2)
происходит раньше (3).
Возвращаясь к вашему примеру, независимо от того, что вы указали memory_order
, гарантируется, что tmpSum += val[i];
произойдетдо sum.fetch_add(tmpSum, std::memory_order_relaxed);
, потому что второй зависит от первого.memory_order
повлияет на возможное изменение порядка команд, которые не влияют на атомарные операции.Например, если бы у вас было int unrelated = 24
.
Btw, официальная терминология «упорядочена до» и «упорядочена после»
В реальном мире аппаратные средства производятвсе немного сложнее.Операции могут появляться в одном порядке в текущем потоке, но другой поток может видеть их в другом порядке, поэтому более строгим memory_order
s приходится применять дополнительные меры для обеспечения согласованного порядка в потоках.
Строго говоря, в этом примере при использовании memory_order_relaxed
у нас будет неопределенное поведение, поскольку доступ к b
не синхронизируется между потоками.