En général, la plus connue des implémentations de référence-comptage intelligent ptr classes en C++, y compris la norme std::shared_ptr
, l'utilisation atomique de comptage de référence, mais ne fournissent pas d'accès atomique à la même puce, ptr instance. En d'autres termes, plusieurs threads peuvent fonctionner en toute sécurité sur des shared_ptr
des cas qui pointent vers le même objet partagé, mais plusieurs threads ne peuvent pas sûre de lire/écrire des instances de la même shared_ptr
de l'instance sans fournir une sorte de synchronisation comme un mutex ou quoi que ce soit.
Atomique version d'un shared_ptr
appelé "atomic_shared_ptr
" a été proposé, et les premiers implémentations existent déjà. Sans doute, atomic_shared_ptr
pourrait facilement être mis en œuvre avec un verrou de rotation ou d'un mutex, mais sans verrouillage de la mise en œuvre est également possible.
Après l'étude de certaines de ces oeuvres, une chose est évidente: la mise en œuvre d'un lock-libre - std::shared_ptr
est très difficile, et il semble qu'besoin de tant compare_and_exchange
des opérations de me faire question de savoir si un simple verrou de rotation permettrait d'atteindre de meilleures performances.
La principale raison pour laquelle il est si difficile à mettre en œuvre un sans verrouillage de référence compté pointeur est en raison de la race qui existe toujours entre la lecture partagée bloc de contrôle (ou de l'objet partagé lui-même, si nous parlons d'un intrusif pointeur partagé), et en modifiant le nombre de références.
En d'autres termes, vous ne pouvez même pas sûre de lire le compteur de référence parce que vous ne savez jamais quand un autre thread a libéré la mémoire où le nombre de références vie.
Donc, en général, complexes et diverses stratégies sont employées pour créer un verrouillage des versions gratuites. La mise en œuvre ici dirait qu'il utilise un double comptage de référence de la stratégie, où il y a des "locaux", les références qui permettent de compter le nombre de threads simultanément accès à l' shared_ptr
instance, puis "partagé" ou "global" des références qui permettent de compter le nombre de shared_ptr instances de pointage de l'objet partagé.
Compte tenu de cette complexité, j'ai été vraiment surpris de trouver un Dr Dobbs article, à partir de 2004 , pas moins (chemin avant C++11 atomics) qui semble nonchalamment résoudre ce problème:
http://www.drdobbs.com/atomic-reference-counting-pointers/184401888
Il ressemble à l'auteur prétend en quelque sorte être en mesure de:
"... [lire] le pointeur vers le compteur incrémente le compteur, et retourne le pointeur-ensemble de telle manière qu'aucun autre thread peut provoquer un résultat incorrect"
Mais je ne comprends pas vraiment la façon dont il fait la met en œuvre. Il utilise (non portable) processeur PowerPC instructions (le LL/SC primitives lwarx
et stwcx
) pour tirer cette off.
Le code qui est fait, est ce qu'il appelle un "aIandF
" (atomique d'incrémentation et de chercher), qu'il définit comme:
addr aIandF(addr r1){
addr tmp;int c;
do{
do{
tmp = *r1;
if(!tmp)break;
c = lwarx(tmp);
}while(tmp != *r1);
}while(tmp && !stwcx(tmp,c+1));
return tmp;
};
Apparemment, addr
est un type de pointeur pointant sur l'objet partagé qui détient le compte de référence variable.
Ma question est: est-ce seulement possible de le faire avec une architecture qui prend en charge LL/SC opérations? Il semble qu'il serait impossible de le faire avec cmpxchg
. Et deuxièmement, exactement comment cela fonctionne? J'ai lu sur ce code quelques temps maintenant, et je ne peux pas vraiment comprendre ce qu'il se passe. Je comprends ce que LL/SC primitives de le faire, je ne peux pas faire tout les sens du code.
Le mieux que je peux comprendre, c'est qu' addr r1
est l'adresse du pointeur de l'objet partagé, et aussi l'adresse du pointeur pour le compte de référence (qui je suppose signifie que le compteur de référence de la variable doit être le premier membre de l' struct
qui définit l'objet partagé). Il a ensuite déréférence addr
(obtenir l'adresse réelle de l'objet partagé). Ensuite, il a lié les charges de la valeur stockée à l'adresse en tmp
, et stocke le résultat dans c
. C'est la valeur du compteur. Il a ensuite conditionnellement stocke cette valeur incrémentée (qui échouera si tmp
a changé) en tmp
.
Ce que je ne comprends pas, c'est comment cela fonctionne. L'adresse de l'objet partagé ne peut jamais changer et le LL/SC pourrait réussir - mais comment est-ce à nous aider si un autre thread a libéré l'objet partagé dans le temps de le dire?