в чем разница между ++, операцией добавления и fetch_add () в atomic () - PullRequest
0 голосов
/ 29 августа 2018

Я запускал следующий код много раз, но почему результат для приращения префикса fetch_add () показывает правильный результат, в то время как при операции add (+) он печатает неверный результат?

#include <iostream>
#include <mutex>
#include <future>
using namespace std;
atomic <int> cnt (0);
void fun()
{
    for(int i =0; i <10000000 ; ++i)
    {
       //++cnt; // print the correct result 20000000 
       //cnt = cnt+1; // print wrong result, arbitrary numbers 
       cnt.fetch_add(1); //  print the correct result 20000000 
    }
}
int main()
{
    auto fut1 = async(std::launch::async, fun);
    auto fut2 = async(std::launch::async, fun);
    fut1.get();
    fut2.get();
    cout << "value of cnt: "<<cnt <<endl;

} 

Ответы [ 3 ]

0 голосов
/ 29 августа 2018

cnt = cnt+1

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

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

Обратите внимание, что оператор ++, --, +=, -=, &=, |=, ^= перегружен в std::atomic для обеспечения атомарных операций.

0 голосов
/ 29 августа 2018

operator ++ - это не отдельная операция, а 3 операции загрузки с добавлением хранилища, и, например, при использовании единой загрузки или хранения на arm64 не возникает никаких препятствий для данных, а объем памяти данных возрастает. Например, atomic_add 1 - это набор кода с семантикой aquire / release

.LBB2_1:            
ldaxr   x8, [x0] //load exclusive register with aquire 
add x8, x8, #1  
stlxr   w9, x8, [x0] //store with rlease
cbnz    w9, .LBB2_1 //if another thread changed value, try again

, где оператор ++ вызовет состояние гонки, если симулирует использование двумя потоками

ldr x8, [x0]
add x8, x8, #1              // =1
str x8, [x0]
0 голосов
/ 29 августа 2018

++cnt и cnt.fetch_add(1) - действительно атомарные операции. Один поток блокируется, в то время как другой читает, увеличивает и обновляет значение. Таким образом, эти два потока не могут наступить друг другу на пальцы. Доступ к cnt полностью сериализован, и окончательный результат будет таким, как вы ожидаете.

cnt = cnt+1; не является полностью атомарным. Он включает в себя три отдельные операции, только две из которых являются атомарными, а одна - нет. К тому времени, когда поток атомарно прочитал текущее значение cnt и сделал копию локально, другой поток больше не блокируется и может свободно изменять cnt по желанию, пока копия увеличивается. Затем присваивание увеличенной копии обратно cnt выполняется атомарно, но будет присваивать устаревшее значение, если cnt уже был изменен другим потоком. Таким образом, конечный результат случайный, а не тот, который вы ожидаете.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...