web-dev-qa-db-fra.com

quelle est la différence entre ++, add operation et fetch_add () dans atomic ()

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;

} 
23
Alok

++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.

29
Remy Lebeau

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.

10
taskinoor

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]
0
Artur Bac