в чем разница между ++, add operation и fetch_add() в atom()
Я запускал следующий код много раз, но почему результат для приращения префикса, fetch_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;
}
Ответы
Ответ 1
++cnt
и cnt.fetch_add(1)
- действительно атомные операции. Один поток блокируется, а другой поток читает, увеличивает и обновляет значение. Таким образом, эти два потока не могут наступать друг на друга. Доступ к cnt
полностью сериализуется, и конечный результат, как и следовало ожидать.
cnt = cnt+1;
не является полностью атомным. Он включает в себя три отдельные операции, только две из которых являются атомарными, но нет. К тому времени, когда поток атомарно считывает текущее значение cnt
и делает его локальным, другой поток больше не блокируется и может свободно изменять cnt
по желанию, пока эта копия увеличивается. Затем назначение инкрементированной копии обратно в cnt
выполняется атомарно, но будет назначать устаревшее значение, если cnt
уже был изменен другим потоком. Таким образом, конечный результат случайный, а не то, что вы ожидаете.
Ответ 2
cnt = cnt+1
Это не атомная операция. Сначала он загружает cnt
в одну атомную операцию, затем добавляет и, наконец, сохраняет результат в другой атомной операции. Тем не менее, значение может быть изменено после загрузки, которое может быть перезаписано конечным хранилищем, что приводит к неверному конечному результату.
Остальные два являются атомными операциями и, таким образом, избегают такого состояния гонки.
Заметим, что оператор ++, --, +=, -=, &=, |=, ^=
перегружен в std::atomic
для обеспечения атомных операций.
Ответ 3
оператор ++ - это не одна операция, но 3 операции загружают хранилище, и, например, для однократной загрузки или хранения на arm64 не генерируют никаких данных, барьер данных. for ex 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
где оператор ++ будет вызывать состояние гонки, если имитационно используется 2 потока
ldr x8, [x0]
add x8, x8, #1 // =1
str x8, [x0]