Question

Quelqu'un connaît-il une implémentation shared_ptr entièrement sécurisée pour les threads? Par exemple. L'implémentation boost de shared_ptr est thread-safe pour les cibles (refcounting) et également sécurisée pour les lectures d'instance shared_ptr simultanées, mais pas pour les écritures ni pour les lectures / écritures.

(voir Boost docs , des exemples 3, 4 et 5).

Existe-t-il une implémentation shared_ptr entièrement thread-safe pour les instances shared_ptr ?

Chose étrange, les documents boosts disent:

  

Les objets shared_ptr offrent le même niveau de sécurité des threads que les types intégrés.

Mais si vous comparez un pointeur ordinaire (type intégré) à smart_ptr , l'écriture simultanée d'un pointeur ordinaire est sûre pour les threads, mais l'écriture simultanée sur un smart_ptr n'est pas.

EDIT: je veux dire une implémentation sans verrouillage sur l’architecture x86.

EDIT2: Un exemple d'utilisation d'un tel pointeur intelligent serait un nombre de threads de travail qui mettent à jour un shared_ptr global avec leur élément de travail actuel et un thread de moniteur qui prélève des échantillons aléatoires des éléments de travail. Shared-ptr serait propriétaire de l'élément de travail jusqu'à ce qu'un autre pointeur d'élément de travail lui soit affecté (détruisant ainsi l'élément de travail précédent). Le moniteur obtiendrait la propriété de l'élément de travail (empêchant ainsi sa destruction) en l'attribuant à son propre ptr partagé. Cela peut être fait avec XCHG et la suppression manuelle, mais ce serait bien si un ptr partagé pouvait le faire.

Un autre exemple est celui où le global-partagé partage un "processeur", est assigné par un thread et utilisé par un autre thread. Lorsque l’utilisateur " utilisateur " Le thread voit que le processeur shard-ptr est NULL, il utilise une logique alternative pour effectuer le traitement. Si ce n'est pas NULL, cela évite la destruction du processeur en l'attribuant à son propre ptr partagé.

Était-ce utile?

La solution

L'ajout des barrières nécessaires à une telle implémentation de shared_ptr entièrement sécurisée pour les threads aurait probablement une incidence sur les performances. Considérez la race suivante (remarque: le pseudocode abonde):

Sujet 1:     global_ptr = A;

Sujet 2:     global_ptr = B;

Sujet 3:     local_ptr = global_ptr;

Si nous décomposons cela en ses opérations constitutives:

Sujet 1:

A.refcnt++;
tmp_ptr = exchange(global_ptr, A);
if (!--tmp_ptr.refcnt) delete tmp_ptr;

Sujet 2:

B.refcnt++;
tmp_ptr = exchange(global_ptr, B);
if (!--tmp_ptr.refcnt) delete tmp_ptr;

Sujet 3:

local_ptr = global_ptr;
local_ptr.refcnt++;

Clairement, si le thread 3 lit le pointeur après l’échange de A, alors B le supprime et le supprime avant que le nombre de références ne puisse être incrémenté, de mauvaises choses se produiront.

Pour gérer cela, nous avons besoin d'une valeur factice à utiliser pendant que le thread 3 effectue la mise à jour de refcnt: (Remarque: compare_exchange (variable, attendu, nouveau) remplace de manière atomique la valeur de variable par new si elle est actuellement égale à new, puis renvoie true si elle l’a réussi.

Sujet 1:

A.refcnt++;
tmp_ptr = global_ptr;
while (tmp_ptr == BAD_PTR || !compare_exchange(global_ptr, tmp_ptr, A))
    tmp_ptr = global_ptr;
if (!--tmp_ptr.refcnt) delete tmp_ptr;

Sujet 2:

B.refcnt++;
while (tmp_ptr == BAD_PTR || !compare_exchange(global_ptr, tmp_ptr, A))
    tmp_ptr = global_ptr;
if (!--tmp_ptr.refcnt) delete tmp_ptr;

Sujet 3:

tmp_ptr = global_ptr;
while (tmp_ptr == BAD_PTR || !compare_exchange(global_ptr, tmp_ptr, BAD_PTR))
    tmp_ptr = global_ptr;
local_ptr = tmp_ptr;
local_ptr.refcnt++;
global_ptr = tmp_ptr;

Vous devez maintenant ajouter une boucle, avec des atomiques au milieu de votre opération / read /. Ce n'est pas une bonne chose - cela peut coûter extrêmement cher sur certains processeurs. De plus, vous êtes également occupé à attendre. Vous pouvez commencer à être malin avec les futex et ainsi de suite - mais à ce stade, vous avez réinventé le verrou.

Ce coût, qui doit être supporté par chaque opération, et qui est très similaire à ce qu'un verrou vous donnerait de toute façon, est la raison pour laquelle vous ne voyez généralement pas de telles implémentations shared_ptr thread-safe. Si vous avez besoin d'une telle chose, je vous conseillerais d'envelopper un mutex et shared_ptr dans une classe pratique pour automatiser le verrouillage.

Autres conseils

L'écriture simultanée sur un pointeur intégré n'est certainement pas thread-safe. Considérez les implications de l’écriture de la même valeur en ce qui concerne les barrières de mémoire si vous voulez vraiment vous rendre fou (par exemple, vous pourriez avoir deux threads pensant que le même pointeur avait des valeurs différentes).

RE: Commenter - la raison pour laquelle les fonctions intégrées ne sont pas supprimées en double, c'est parce qu'elles ne le sont pas du tout (et que l'implémentation de boost :: shared_ptr que j'utilise ne supprime pas en double, car il utilise un incrément atomique spécial. et décrémenter, donc il ne ferait que supprimer, mais le résultat aurait alors le pointeur de l’un et le décompte de l’autre, ou à peu près n'importe quelle combinaison des deux. Ce serait mauvais.). La déclaration dans la documentation boost est correcte, car vous obtenez les mêmes garanties que si vous utilisiez une fonction intégrée.

RE: EDIT2 - La première situation que vous décrivez est très différente entre l’utilisation des fonctions intégrées et les shared_ptrs. Dans l'un (XCHG et suppression manuelle), il n'y a pas de compte de références; vous supposez que vous en êtes le seul et unique propriétaire. Si vous utilisez des pointeurs partagés, vous dites que d'autres threads peuvent être propriétaires, ce qui rend les choses beaucoup plus complexes. Je pense que cela est possible avec un système de comparaison, mais ce serait très peu portable.

C ++ 0x est en train de sortir une bibliothèque atomics, ce qui devrait rendre beaucoup plus facile l'écriture de code générique multi-thread. Vous devrez probablement attendre que cela apparaisse pour voir de bonnes implémentations de références multi-plateformes de pointeurs intelligents thread-safe.

Je ne connais pas une telle implémentation de pointeur intelligent, même si je dois demander: comment ce comportement pourrait-il être utile? Les seuls scénarios auxquels je peux penser où vous pourriez trouver des mises à jour simultanées de pointeur sont des conditions de concurrence (bogues).

Il ne s’agit pas d’une critique - il se peut qu’il s’agisse d’un cas d’utilisation légitime, je ne peux tout simplement pas y penser. S'il vous plaît faites le moi savoir!

Objet: EDIT2 Merci de nous avoir fourni quelques scénarios. Il semble que les écritures de pointeur atomique seraient utiles dans ces situations. (Une petite chose: pour le deuxième exemple, lorsque vous écrivez "Si ce n'est pas NULL, cela empêche le processeur d'être détruit en l'attribuant à son propre ptr partagé", j'espère que vous vouliez dire que vous affectez le pointeur partagé global à le pointeur partagé local puis vérifie d'abord si le pointeur partagé local est NULL - comme vous l'avez décrit, il est sujet à une situation de concurrence critique dans laquelle le pointeur partagé global devient NULL après son test et avant son affectation. à la locale.)

Vous pouvez utiliser cette implémentation pointeurs de comptage de références atomiques pour au moins la mise en œuvre. le mécanisme de comptage des références.

Votre compilateur peut déjà fournir les pointeurs intelligents protégés pour les threads dans les nouvelles normes C ++. Je crois que TBB envisage d'ajouter un pointeur intelligent, mais je ne pense pas qu'il a été inclus pour le moment. Cependant, vous pourrez peut-être utiliser l’un des conteneurs sans risque de threads de TBB.

Vous pouvez facilement le faire en incluant un objet mutex avec chaque pointeur partagé et en encapsulant les commandes d'incrémentation / de décrémentation avec le verrou.

Je ne pense pas que cela soit si facile, cela ne suffit pas d'envelopper vos classes sh_ptr avec un CS. Il est vrai que si vous gérez un seul CS pour tous les pointeurs partagés, vous éviterez ainsi mutuellement l’accès et la suppression des objets sh_ptr entre différents threads. Mais ce serait terrible, un objet CS pour chaque pointeur partagé constituerait un véritable goulot d'étranglement. Il serait approprié que tous les nouveaux ptr-s enveloppables aient des CS différents, mais nous devrions ainsi créer notre CS de manière dinamique et veiller à ce que les copieurs des classes sh_ptr transmettent ces Cs partagés. Nous sommes maintenant arrivés au même problème: qui met en quarantaine que ce Ctr est déjà supprimé ou non. Nous pouvons être un peu plus intelligents avec des indicateurs volatils m_bReleased par instance, mais de cette manière, nous ne pouvons pas bloquer les écarts de sécurité entre la vérification de l'indicateur et l'utilisation des Cs partagés. Je ne vois pas de solution complètement sûre pour ce problème. Peut-être que ce terrible C mondial serait le mineur qui tue l'application. (désolé pour mon anglais)

Cela peut ne pas être exactement ce que vous souhaitez, mais la documentation boost :: atomic fournit un exemple d'utilisation d'un compteur atomique avec intrusive_ptr . intrusive_ptr est l’un des pointeurs intelligents Boost, il effectue un "comptage de références intrusif", ce qui signifie que le compteur est "intégré". dans la cible au lieu de fournir par le pointeur intelligent.

Boost atomic Exemples d'utilisation:

http://www.boost.org/doc/html/atomic/ usage_examples.html

À mon avis, la solution la plus simple consiste à utiliser un intrusive_ptr avec quelques modifications mineures (mais nécessaires).

J'ai partagé mon implémentation ci-dessous:

http://www.philten.com/boost-smartptr-mt/

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top