Понимание последовательностей памяти и std :: memory_order_relaxed - PullRequest
0 голосов
/ 09 октября 2018

Я изучаю последовательности памяти 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 ]

0 голосов
/ 09 октября 2018

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 не синхронизируется между потоками.

0 голосов
/ 09 октября 2018

Значит, сумма равна 0? ??

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

0 голосов
/ 09 октября 2018

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

Нет

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

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

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

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

0 голосов
/ 09 октября 2018

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

...