Понимание последовательностей памяти и std :: memory_order_relaxed

Я изучаю последовательности памяти C ++, но это очень запутанно.

Например:

void sumUp(std::atomic<int>& sum, std::vector<int>& val)
{
   int tmpSum = 0;
   for(auto i = 0; i < 100; ++i) tmpSum += val[i];

   sum.fetch_add(tmpSum, std::memory_order_relaxed);
}

Я не понимаю, что sum.fetch_add() работает после tmpSum += val[i] . Так как это не в порядке, можете sum.fetch_add() работать до tmpSum += val[i] ?

Так возможна ли сумма 0 ?

Большое спасибо.

Всего 4 ответа


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 вы укажете memory_order , гарантируется, что tmpSum += val[i]; произойдет до sum.fetch_add(tmpSum, std::memory_order_relaxed); потому что второй зависит от первого. memory_order повлияет на возможное переупорядочение инструкций, которые не влияют на работу атома. Например, если бы у вас был бы int unrelated = 24 .


Кстати, официальная терминология «секвенирована раньше» и «упорядочена после»,


В реальном мире оборудование делает вещи немного сложнее. Операции могут отображаться в одном порядке в текущем потоке, но другой поток может видеть их в другом порядке, поэтому более строгие memory_order s должны использовать дополнительные меры для обеспечения согласованного порядка потоков.


Строго говоря в этом примере, если вы используете memory_order_relaxed у нас было бы Undefined Behavior, потому что доступ к b не синхронизирован по потокам.


Значит, сумма 0 возможна ??

Нет, это не так. std::memory_order_relaxed говорит, что одновременный доступ к sum обычно не упорядочен; в то же время в этом конкретном потоке вычисление tmpSum секвенируется перед fetch_add , поэтому значение, переданное fetch_add , согласуется со значением, вычисленным в цикле. Таким образом, fetch_add не гарантирует, в каком порядке все отдельные потоковые tmpSum s добавляются по всем потокам, но это не имеет никакого значения, так как интегральное сложение коммутирует; но семантика языка гарантирует, что добавление с добавлением значения представляет собой сумму вектора каждый раз.


Так как это не в порядке, может ли sum.fetch_add () работать до tmpSum + = val [i]? Значит, сумма 0 возможна ??

нет

Независимо от переупорядочения нагрузок и хранилищ в результате правила as-if, программа должна по-прежнему вести себя как «если», то код выполнялся в явном порядке письменных инструкций.

что означает std::memory_order_relaxed :

  • добавление будет происходить атомарно относительно других атомных операций по сумме, которые происходят в разных потоках.

  • изменение суммы не может быть замечено другой нитью немедленно, но это будет наблюдаться очень скоро.


Нет, почему? Операторы выполняются в порядке, что означает сначала 100 операций добавления, а затем fetch_add . Поскольку вы используете атомный, я думаю, вы делаете многопоточность. Может быть, если функция выполняется несколько раз параллельно, то одиночные fetch_add происходят в произвольном порядке.


Есть идеи?

10000