J'ai exécuté le code suivant plusieurs fois, mais pourquoi le résultat de l'incrément de préfixe, fetch_add (), indique le résultat correct alors qu'avec l'ajout de l'opération (+), le résultat est incorrect?
#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;
}
++cnt
et cnt.fetch_add(1)
sont de véritables opérations atomiques. Un thread est bloqué pendant que l'autre thread lit, incrémente et met à jour la valeur. En tant que tels, les deux threads ne peuvent pas marcher l'un sur l'autre. L'accès à cnt
est entièrement sérialisé et le résultat final est conforme à vos attentes.
cnt = cnt+1;
n'est pas totalement atomique. Il implique trois opérations distinctes, dont deux seulement sont atomiques, mais une ne l’est pas. Au moment où un thread a lu de manière atomique la valeur actuelle de cnt
et en a fait un copy localement, l'autre thread n'est plus bloqué et peut librement modifier cnt
à volonté pendant que copy est incrémenté. Ensuite, l'attribution du copy incrémenté à cnt
est effectuée de manière atomique, mais elle affectera une valeur périmée si cnt
a déjà été modifié par l'autre thread. Donc, le résultat final est aléatoire et pas ce que vous attendez.
cnt = cnt+1
Ce n'est pas une opération atomique. Ceci charge d'abord cnt
dans une opération atomique, puis ajoute et enregistre le résultat dans une autre opération atomique. Cependant, la valeur peut être modifiée après le chargement et peut être écrasée par le magasin final, ce qui conduit à un résultat final erroné.
Les deux autres sont des opérations atomiques et évitent ainsi une telle situation de concurrence critique.
Notez que les opérateurs ++, --, +=, -=, &=, |=, ^=
sont surchargés dans std::atomic
pour permettre des opérations atomiques.
operator ++ n'est pas une opération unique mais 3 opérations load add store, et par exemple sur arm64, le chargement ou le stockage simple ne génère aucune clôture de données, la mémoire de données inférieure ./sémantique de 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
où l'opérateur ++ causera une condition de concurrence critique s'il est utilisé simultanément par 2 threads
ldr x8, [x0]
add x8, x8, #1 // =1
str x8, [x0]