7
votes

parallèle.foreach fonctionne, mais pourquoi?

Quelqu'un peut-il expliquer, pourquoi ce programme renvoie la valeur correcte pour sqrt_min?

int n = 1000000;

double[] myArr = new double[n];
for(int i = n-1 ; i>= 0; i--){ myArr[i] = (double)i;}

// sqrt_min contains minimal sqrt-value
double sqrt_min = double.MaxValue;

Parallel.ForEach(myArr, num =>
{
double sqrt = Math.Sqrt(num); // some time consuming calculation that should be parallized
if(sqrt < sqrt_min){ sqrt_min = sqrt;}
});
Console.WriteLine("minimum: "+sqrt_min);


2 commentaires

Voir aussi Stackoverflow.com/Questtions/3679209/... Cela peut ne pas avoir de chance. Ce peut être parce que votre CPU délivre des doubles opérations atomiques même si c # ne le garantit pas.


Il y a une possibilité que ce bogue puisse introduire suffisamment de conflit de verrouillage qu'il sera plus lent que de calculer les racines carrées sur un fil. Une meilleure solution serait d'avoir chaque CPU calculer de nombreuses racines carrées, en gardant une trace de leurs propres minimums et de trouver le minimum mondial lorsque chaque processeur est terminé. Vous aurez toujours des contentions, mais la conflit ne s'appliquera que tout en comparant les minimums locaux spécifiques au thread, plutôt que pour chaque racine carrée.


5 Réponses :


5
votes

Votre code n'est pas sûr; Cela ne fonctionne que par coïncidence.

Si deux threads exécutent le si simultanément, l'un des minimums sera écrasé:

  • sqrt_min = 6
  • thread a: sqrt = 5
  • thread b: sqrt = 4
  • thread a entre dans le si
  • thread b entre le si
  • thread b attribue sqrt_min = 4
  • thread a assigné sqrt_min = 5

    sur des systèmes 32 bits, vous êtes également vulnérable à lire / écrire une déchirure.

    Il serait possible de rendre cette sécurité en utilisant interlocked.careeexchange dans une boucle.


4 commentaires

c'est ce que je pensais! Mais je n'ai jamais eu à ce point! Même en essayant très fort et beaucoup de fois> 10 ^ 9


@ user1193134: La taille de la matrice n'a pas d'importance (sauf si vous avez des centaines de cœurs). Utilisation de la modification de Dasblinkenlight, je reçois des échecs cohérents.


Vous semblez très familier avec la chose interlocked.careExchange. Puissiez-vous me donner une main avec le problème?


Malheureusement, je n'ai pas le temps maintenant; Pardon.



13
votes

Cela fonctionne par une chance pure. Parfois, lorsque vous l'exécutez, vous êtes lucky que le non-atomique lit et écrit au double ne résulte pas des valeurs "déchirées". Parfois, vous êtes chanceux que les tests non atomiques et les ensembles se produisent simplement pour définir la valeur correcte lorsque cette course arrive. Rien ne garantit que ce programme produit un résultat particulier.


3 commentaires

@ user1193134: En général, des verrous ou des opérations atomiques (extrêmement prudentes). Dans votre cas particulier, plinq agrégat () ou juste min () .


Pourriez-vous me donner une main pour résoudre ce problème?


@ user1193134: Je pense que je vous ai déjà aidé. Parallel Foreach ne fait pas magiquement votre code threadsafe. Si vous voulez que ce soit Threadsafe, le faire threadsafe . Comment choisir de faire cela est à vous de décider; Je ne sais pas quelles sont vos exigences.



3
votes

Votre code ne fonctionne pas vraiment: je l'ai couru dans une boucle 100 000 fois, et il a échoué une fois sur mon ordinateur 8 cœurs, produisant cette sortie:

static void Run() {
    int n = 10;

    double[] myArr = new double[n];
    for (int i = n - 1; i >= 0; i--) { myArr[i] = (double)i*i; }

    // sqrt_min contains minimal sqrt-value
    double sqrt_min = double.MaxValue;

    Parallel.ForEach(myArr, num => {
        double sqrt = Math.Sqrt(num); // some time consuming calculation that should be parallized
        if (sqrt < sqrt_min) { sqrt_min = sqrt; }
    });
    if (sqrt_min > 0) {
        Console.WriteLine("minimum: " + sqrt_min);
    }
}


static void Main() {
    for (int i = 0; i != 100000; i++ ) {
        Run();
    }
}


0 commentaires

2
votes

Comme d'autres l'ont dit, cela ne fonctionne que sur la chance de cisaillement. Les affiches OP et d'autres ont eu des difficultés créant la condition de course cependant. Qui est assez facile expliqué. Le code génère de nombreuses conditions de race, mais la grande majorité d'entre eux (99,9999% pour être exact) ne sont pas pertinents. Tout ce qui compte à la fin de la journée est le fait que 0 devrait être le résultat min. Si votre code pense que la racine 5 est supérieure à la racine 6, ou que la racine 234 est supérieure à la racine 235, elle ne casse toujours pas. Il doit y avoir une condition de course spécifiquement avec l'itération générant 0. Les chances que l'une des itérations a une condition de race avec une autre est très très élevée. Les chances que le traitement de l'itération Le dernier article a une condition de race est vraiment assez faible.


0 commentaires

4
votes

Pour la raison pour laquelle votre code d'origine est brisé, vérifiez les autres réponses, je ne vais pas répéter cela.

La multithreading est plus facile quand il n'y a pas d'accès en écriture à l'état partagé. Heureusement, votre code peut être écrit de cette façon. LINQ parallèle peut être agréable dans de telles situations, mais parfois, la surcharge est trop grande. P>

Vous pouvez réécrire votre code à: p> xxx pré>

dans votre spécifique Problème Il est plus rapide d'échanger autour de l'opération min code> et de l'opération sqrt code>, ce qui est possible car sqrt code> augmente monotoniquement. P>

double sqrt_min = Math.Sqrt(myArr.AsParallel().Min())


0 commentaires