La compréhension de std::atomique::compare_exchange_weak() en C++11
bool compare_exchange_weak (T& expected, T val, ..);
compare_exchange_weak()
est l'un de comparer échange de primitives fournies en C++11. C'est faible dans le sens où elle renvoie false, même si la valeur de l'objet est égale à expected
. Cela est dû à fausse panne sur certaines plates-formes où une séquence d'instructions (au lieu d'un seul comme sur x86) sont utilisés pour la mettre en œuvre. Sur de telles plates-formes, changement de contexte, le rechargement de la même adresse (ou de la ligne de cache) par un autre thread, etc peut échouer la primitive. C'est spurious
que ce n'est pas la valeur de l'objet (n'est pas égal à expected
) qui n'a pas l'opération. Au lieu de cela, c'est une sorte de problèmes de timing.
Mais ce qui m'intrigue, c'est ce qui est dit dans C++11 Norme (ISO/IEC 14882),
29.6.5
..
Une conséquence de la fausse panne, c'est que presque toutes les utilisations de la faiblesse des
comparez et d'échange sera dans une boucle.
Pourquoi a-t-il d'être dans une boucle dans presque toutes les utilisations ? Est-ce à dire que nous ne boucle lorsqu'il échoue à cause de fausses pannes? Si c'est le cas, pourquoi ne nous dérange pas utiliser compare_exchange_weak()
et écrire la boucle de nous-mêmes? Nous pouvons simplement utiliser compare_exchange_strong()
qui, je pense, devrait se débarrasser de la fausse échecs pour nous. Quels sont les cas d'utilisation courants de compare_exchange_weak()
?
Une autre question. Dans son livre "C++ de la Simultanéité Dans l'Action" Anthony dit,
//Because compare_exchange_weak() can fail spuriously, it must typically
//be used in a loop:
bool expected=false;
extern atomic<bool> b; //set somewhere else
while(!b.compare_exchange_weak(expected,true) && !expected);
//In this case, you keep looping as long as expected is still false,
//indicating that the compare_exchange_weak() call failed spuriously.
Pourquoi est !expected
il y a dans la condition de la boucle? Est-il là pour éviter que tous les threads peuvent mourir de faim et ne fait aucun progrès depuis un certain temps?
Edit: une dernière question)
Sur les plates-formes qu'aucun matériel CAS d'instructions en vigueur, les faibles et les forts de la version sont mises en œuvre à l'aide de LL/SC (comme ARM, PowerPC, etc). Donc, il y a une différence entre les deux boucles? Pourquoi, si? (Pour moi, ils devraient avoir des performances similaires.)
//use LL/SC (or CAS on x86) and ignore/loop on spurious failures
while (!compare_exchange_weak(..))
{ .. }
//use LL/SC (or CAS on x86) and ignore/loop on spurious failures
while (!compare_exchange_strong(..))
{ .. }
Je viens w/cette dernière question les gars, vous tous mentionner qu'il ya peut-être une différence de performance à l'intérieur d'une boucle. Il est également mentionné par le C++11 Norme (ISO/IEC 14882):
Quand a comparer-et-exchange est dans une boucle, la version faible rendement
de meilleures performances sur certaines plates-formes.
Mais comme analysé ci-dessus, les deux versions dans une boucle doit donner le même rendement. Quelle est la chose qui me manque?
- W/r/t à la première question, dans de nombreux cas, vous avez besoin de boucle, de toute façon (si vous utilisez le fort ou le faible version), et la version faible, peut avoir de meilleures performances que le fort.
- À la fois faible et forte autorités de certification sont mis en œuvre "à l'aide de LL/SC", de la même manière que les deux tri à bulles et quicksort sont mis en œuvre "à l'aide de swap"; c'est, dans le sens que c'est la primitive de fonctionnement utilisé pour obtenir la tâche accomplie. Ce qu'ils enrouler autour de LL/SC est très différent. La faiblesse de la SAE est juste LL/SC. Forte SAE LL/SC avec un tas d'autres trucs.
- forums.manning.com/posts/list/33062.page est-il de l'aide?
- avec la réponse dans ce lien, je ne vois pas pourquoi "la version faible, d'obtenir de meilleurs performances sur certaines plates-formes", comme indiqué dans la Norme.
- Sur d'autres, compare_exchange_weak peut échouer faussement, en raison des interruptions ou des actions d'autres processeurs ou les threads. Sur ces plateformes, compare_exchange_strong est effectivement une boucle sur compare_exchange_weak - si elle n'a pas faussement puis il les boucles de nouveau. Est-il utile? Peut-être que je me trompe
Vous devez vous connecter pour publier un commentaire.
, Pourquoi le faire échanger dans une boucle?
Habituellement, vous voulez que votre travail soit fait avant de vous déplacer, donc, vous mettez
compare_exchange_weak
dans une boucle de sorte qu'il essaie d'échange jusqu'à ce qu'il réussit (c'est à dire, les retourstrue
).Notez que
compare_exchange_strong
est souvent utilisé dans une boucle. Il ne manque pas due à des parasites de l'échec, mais il n'échouent en raison des écritures simultanées.Pourquoi utiliser
weak
au lieu destrong
?Assez facile: Fausse panne ne se produit pas souvent, donc c'est pas un gros gain de performance. En contraste, en tolérant un échec, qui permet une bien plus efficace la mise en œuvre de la
weak
version (en comparaison àstrong
) sur certaines plates-formes:strong
doit toujours vérifier les parasites de l'échec et de la masquer. C'est cher.Ainsi,
weak
est utilisé parce qu'il est beaucoup plus rapide questrong
sur certaines plates-formesQuand devez-vous utiliser
weak
et quandstrong
?La référence membres de conseils lors de l'utilisation de
weak
et quand utiliserstrong
:Donc, la réponse semble être assez simple à retenir: Si vous devez introduire une boucle seulement à cause de parasites de l'échec, de ne pas le faire; utilisation
strong
. Si vous avez une boucle de toute façon, puis utilisezweak
.Pourquoi est
!expected
dans l'exempleIl dépend de la situation et de sa valeur sémantique, mais généralement, il n'est pas nécessaire pour l'exactitude. En omettant il serait rendement très semblable à la sémantique. Seulement dans un cas où un autre thread peut réinitialiser la valeur de
false
, la sémantique pourrait devenir légèrement différente (mais je ne peux pas trouver un exemple significatif endroit où vous voulez que). Voir Tony D. commentaire pour une explication détaillée.C'est simplement une voie rapide quand un autre thread écrit
true
: Puis le nous abandonner au lieu d'essayer d'écriretrue
de nouveau.Sur votre dernière question
De Wikipédia:
Donc, LL/SC échouera faussement changement de contexte, par exemple. Maintenant, la version forte de apporterait sa "petite boucle" de détecter les parasites de l'échec et de la masquer par essayer de nouveau. Notez que la boucle est également plus complexe qu'un simple CAS de la boucle, car il faut distinguer entre les parasites de l'échec (et masque) et l'échec en raison de l'accès simultané (qui se traduit par un retour à la valeur
false
). La version faible, n'a pas de boucles.Puisque vous fournir explicite boucle dans les deux exemples, il n'est tout simplement pas nécessaire d'avoir la petite boucle pour la version forte. Par conséquent, dans l'exemple de la
strong
version, la vérification de l'échec est fait en deux fois: une fois parcompare_exchange_strong
(ce qui est plus compliqué car il faut distinguer les parasites de l'échec et simultanées acces) et une fois par votre boucle. Ce cher vérifier est pas nécessaire et que la raison pourquoiweak
sera plus rapide ici.Notez également que votre argument (LL/SC) est à seulement un possibilité de le mettre en œuvre. Il y a plus de plates-formes qui ont même des jeux d'instructions différents. En plus (et surtout), il faut noter que
std::atomic
doit prendre en charge toutes les opérations pour tous les types de données possibles, de sorte que même si vous déclarez une dizaine de millions d'octets struct, vous pouvez utilisercompare_exchange
sur cette. Même quand sur un CPU qui n'ont CAS, vous ne pouvez pas le CAS de dix millions d'octets, de sorte que le compilateur va générer d'autres instructions (probablement de verrouillage acquérir, suivi par un non-atomique de comparer et d'échange, suivie d'une libération du verrou). Maintenant, pensez à combien de choses peuvent se produire tout en échangeant les dix millions d'octets. Ainsi, alors que une fausse erreur peut être très rare pour 8 octets d'échanges, il peut être plus commun dans ce cas.Donc, en un mot, C++ vous donne deux sémantique, un "best effort" (les
weak
) et un "je vais le faire pour vous, n'importe comment beaucoup de mauvaises choses peuvent se produire entre les deux" un (strong
). Comment ceux-ci sont mis en œuvre sur les différents types de données et plates-formes est un tout autre sujet. N'attachez pas votre modèle mental de la mise en œuvre sur votre plate-forme spécifique; la bibliothèque standard est conçu pour fonctionner avec d'autres architectures que vous pourriez être conscients de. La seule conclusion que nous pouvons tirer est que la garantie de la réussite est généralement plus difficile (et donc requièrent plus de travail) que juste essayer et laissant de la place pour l'échec possible.b
est déjàtrue
, puis - avecexpected
maintenanttrue
- sans&& !expected
en boucle et essaie une autre (stupide) échange detrue
ettrue
qui peut bien "réussir" trivialement de rupture de l'while
boucle, mais pourrait présenter de façon significative comportement différent sib
avait entre-temps changé de nouveau àfalse
, auquel cas, la boucle continue et peut, finalement,b
true
encore avant de casser.compare_exchange_strong()
peut échouer en raison d'écritures simultanées? Cela semble basique, mais je ne trouve pas de confirmation sur cppreference.com.Parce que si vous n'avez pas de boucle et il ne parvient pas faussement votre programme n'a pas fait quelque chose d'utile - vous n'avez pas de mise à jour de l'objet atomique et vous ne savez pas ce que sa valeur actuelle est (Correction: voir commentaire ci-dessous à partir de Cameron). Si l'appel n'est pas de faire quelque chose d'utile, quel est le point de le faire?
Oui.
Sur certaines architectures
compare_exchange_weak
est plus efficace, et des défaillances parasites doit être assez rare, donc il pourrait être possible d'écrire des algorithmes plus efficaces à l'aide de la forme faible et une boucle.En général, il est probablement préférable d'utiliser la version forte de la place si votre algorithme n'a pas besoin de boucle, que vous n'avez pas besoin de s'inquiéter à propos des défaillances parasites. Si elle a besoin de boucle de toute façon, même pour la version forte (et de nombreux algorithmes avez besoin de boucle de toute façon), puis à l'aide de la forme faible pourrait être plus efficace sur certaines plates-formes.
La valeur pourraient avoir été mis à
true
par un autre thread, de sorte que vous ne souhaitez pas conserver en boucle en essayant de la définir.Edit:
Sûrement, il est évident que sur les plates-formes où les parasites de l'échec est possible, la mise en œuvre de
compare_exchange_strong
doit être plus compliqué, pour vérifier les parasites de l'échec et de réessayer.La forme faible renvoie simplement sur la base de fausses échec, il n'a pas réessayer.
you don't know what its current value is
dans le 1er point, quand une fausse panne a lieu, ne devrait-il pas une valeur égale à la valeur prévue à cet instant? Sinon, ce serait un véritable échec.while(!compare_exchange_weak(..))
etwhile(!compare_exchange_strong(..))
?Je vais essayer de répondre à moi-même, après être passé par diverses ressources en ligne (par exemple, cette une et cette une), le C++11 Standard, ainsi que les réponses données ici.
Les questions sont regroupées (par exemple, "pourquoi !prévu ?" est fusionné avec "pourquoi mettre compare_exchange_weak() dans une boucle ?") et les réponses sont données en conséquence.
Pourquoi ne compare_exchange_weak() doivent être dans une boucle dans presque toutes les utilisations?
Modèle Typique D'Un
Vous avez besoin de réaliser une mise à jour atomique basé sur la valeur de la variable atomique. Un échec indique que la variable n'est pas mise à jour avec notre valeur souhaitée et nous voulons réessayer il. Notez que nous n'avons pas vraiment se soucier de savoir s'il échoue en raison d'écriture simultanées ou fausse panne. Mais nous ne de soins que c'est nous qui font de ce changement.
D'un exemple réel pour plusieurs threads pour ajouter un élément à une seule liste liée simultanément. Chaque thread chargé pour la première fois la tête du pointeur, alloue un nouveau nœud et l'ajoute à la tête de ce nouveau nœud. Enfin, il tente d'échanger le nouveau nœud avec la tête.
Un autre exemple est la mise en œuvre mutex en utilisant
std::atomic<bool>
. Au plus un seul thread peut accéder à la section critique à la fois, en fonction sur le thread première sériecurrent
àtrue
et sortir de la boucle.Modèle Typique B
C'est en fait le motif mentionné dans Anthony du livre. Contrairement à Un modèle, vous voulez la variable atomique à être mis à jour une fois, mais vous n'avez pas de soins qui le fait. Tant qu'il n'est pas mis à jour, que vous essayez à nouveau. Ceci est généralement utilisé avec des variables booléennes. E. g., vous avez besoin de mettre en œuvre un déclencheur pour une machine d'état de se déplacer. Le thread qui a appuyé sur la gâchette est indépendamment.
Noter que nous ne sont généralement pas utiliser ce modèle pour mettre en œuvre un mutex. Sinon, plusieurs threads peuvent être à l'intérieur de la section critique en même temps.
Cela dit, il doit être rare d'utiliser
compare_exchange_weak()
en dehors d'une boucle. Au contraire, il y a des cas que la version forte est en cours d'utilisation. E. g.,compare_exchange_weak
n'est pas de mise ici, parce que quand il retourne en raison de parasites de l'échec, il est probable que personne n'occupe la section critique encore.De Faim Fil?
Un point à mentionner est que ce qui se passe si les fausses pannes de continuer à se produire donc d'en priver le fil? Théoriquement, il pourrait se produire sur des plates-formes
compare_exchange_XXX()
est de mettre en œuvre une séquence d'instructions (par exemple, LL/SC). Fréquents accès de la même ligne de cache entre LL et SC va produire en continu des défaillances parasites. De façon plus réaliste exemple est dû à un muet de planification où tous les threads simultanés sont entrelacés de la manière suivante.Peut-il arriver?
Il ne se passe pas toujours, heureusement, grâce à ce que le C++11 exige:
Pourquoi ne nous dérange pas utiliser compare_exchange_weak() et écrire la boucle de nous-mêmes? Nous pouvons simplement utiliser compare_exchange_strong().
Il dépend.
Cas 1: Lorsque les deux doivent être utilisés à l'intérieur d'une boucle. C++11 dit:
Sur x86 (au moins actuellement. Peut-être que ça va recourir à un semblable régime LL/SC un jour, pour des performances lorsque plus de cœurs sont introduits), les faibles et les forts de la version sont essentiellement les mêmes, car ils se résument à la seule instruction
cmpxchg
. Sur d'autres plates-formes oùcompare_exchange_XXX()
n'est pas mis en œuvre atomiquement (ici, pas d'équipement unique primitive existe), la version faible à l'intérieur de la boucle peut gagner la bataille, à cause de la forte que l'on aura à gérer le faux échecs et de réessayer en conséquence.Mais,
rarement, on peut préférer
compare_exchange_strong()
surcompare_exchange_weak()
même dans une boucle. E. g., quand il y a beaucoup de choses à faire entre la variable atomique est chargé et calculé de la nouvelle valeur en échange (voirfunction()
ci-dessus). Si la variable atomique elle-même ne change pas souvent, nous n'avons pas besoin de répéter le coût de calcul pour tous les parasites de l'échec. Au lieu de cela, nous pouvons espérer quecompare_exchange_strong()
"absorber" ces échecs et de nous contenter de répéter le calcul quand il échoue en raison d'un réel changement de valeur.Cas 2: Lorsque seule
compare_exchange_weak()
doivent être utilisés à l'intérieur d'une boucle. C++11 dit aussi:C'est généralement le cas lorsque vous boucle afin d'éliminer les fausses pannes à partir de la version faible. Vous recommencez jusqu'à ce que l'échange est un succès ou un échec à cause de l'écriture simultanées.
Au mieux, c'est réinventer la roue et d'effectuer les mêmes que
compare_exchange_strong()
. Le pire? Cette approche ne permet pas de profiter pleinement des machines qui fournissent des non-faux de comparer et d'échange de matériel.Dernier, si vous boucle pour d'autres choses (par exemple, voir "Schéma Typique d'Un" ci-dessus), alors il ya une bonne chance que
compare_exchange_strong()
doit également être mis dans une boucle, ce qui nous ramène au cas précédent.Bon, alors j'ai besoin d'une fonction qui réalise atomique gauche-shifting. Mon processeur n'a pas un fonctionnement natif pour cette, et la bibliothèque standard n'a pas une fonction pour ça, donc, on dirait que je suis en train d'écrire mon propre. Va ici:
Maintenant, il y a deux raisons pour que la boucle peut être exécuté plus d'une fois.
Honnêtement, je ne se soucient pas qui un. Gauche décalage est assez rapide pour que je puisse tout aussi bien le faire à nouveau, même si l'échec était fausse.
Ce qui est moins rapide, cependant, est le code supplémentaire que la forte SAE doit enrouler autour de la faiblesse des autorités de certification pour être forte. Ce code ne fait pas beaucoup quand la faiblesse de la SAE réussit... mais quand il échoue, forte SAE doit faire un travail de détective à déterminer si c'était le Cas 1 ou le Cas 2. Que le travail de détective prend la forme d'une deuxième boucle, de manière efficace à l'intérieur de mon propre boucle. Deux boucles imbriquées. Imaginez votre algorithmes enseignant flagrante à vous maintenant.
Et comme je l'ai mentionné précédemment, je n'ai pas de soins sur le résultat de ce travail de détective! De toute façon, je vais refaire le CAS. Donc, en utilisant des TAS de gains en moi rien de précis, et perd de moi, mais une petite quantité mesurable de l'efficacité.
En d'autres termes, la faiblesse de l'AC est utilisé pour mettre en œuvre atomique opérations de mise à jour. Fort de CAS est utilisée lorsque vous vous souciez du résultat de la scs.