std :: shared_ptr sécurité des threads expliquée

106

Je lis http://gcc.gnu.org/onlinedocs/libstdc++/manual/shared_ptr.html et certains problèmes de sécurité des threads ne sont toujours pas clairs pour moi:

  1. Standard garantit que le comptage de références est géré thread-safe et qu'il est indépendant de la plateforme, non?
  2. Problème similaire - la norme garantit qu'un seul thread (contenant la dernière référence) appellera delete sur un objet partagé, non?
  3. shared_ptr ne garantit aucune sécurité de thread pour les objets qui y sont stockés?

ÉDITER:

Pseudo code:

// Thread I
shared_ptr<A> a (new A (1));

// Thread II
shared_ptr<A> b (a);

// Thread III
shared_ptr<A> c (a);

// Thread IV
shared_ptr<A> d (a);

d.reset (new A (10));

L'appel de reset () dans le thread IV supprimera l'instance précédente de la classe A créée dans le premier thread et la remplacera par une nouvelle instance? De plus, après avoir appelé reset () dans le thread IV, les autres threads ne verront que l'objet nouvellement créé?

Dingo
la source
24
Droite, droite et droite.
spraff le
16
vous devriez utiliser à la make_sharedplace denew
qdii

Réponses:

87

Comme d'autres l'ont souligné, vous l'avez bien compris en ce qui concerne vos 3 questions d'origine.

Mais la dernière partie de votre modification

L'appel de reset () dans le thread IV supprimera l'instance précédente de la classe A créée dans le premier thread et la remplacera par une nouvelle instance? De plus, après avoir appelé reset () dans le thread IV, les autres threads ne verront que l'objet nouvellement créé?

est incorrect. Seulement dpointera vers le nouveau A(10), et a, bet ccontinuera à le point à l'original A(1). Cela peut être clairement vu dans le court exemple suivant.

#include <memory>
#include <iostream>
using namespace std;

struct A
{
  int a;
  A(int a) : a(a) {}
};

int main(int argc, char **argv)
{
  shared_ptr<A> a(new A(1));
  shared_ptr<A> b(a), c(a), d(a);

  cout << "a: " << a->a << "\tb: " << b->a
     << "\tc: " << c->a << "\td: " << d->a << endl;

  d.reset(new A(10));

  cout << "a: " << a->a << "\tb: " << b->a
     << "\tc: " << c->a << "\td: " << d->a << endl;
                                                                                                                 
  return 0;                                                                                                          
}

(De toute évidence, je ne me suis pas dérangé avec le filetage: cela ne tient pas compte du shared_ptr::reset()comportement.)

La sortie de ce code est

a: 1 b: 1 c: 1 d: 1

a: 1 b: 1 c: 1 d: 10

Nicu Stiurca
la source
35
  1. Correct, shared_ptrs utilise des incréments / décréments atomiques d'une valeur de comptage de référence.

  2. La norme garantit qu'un seul thread appellera l'opérateur de suppression sur un objet partagé. Je ne suis pas sûr qu'il spécifie spécifiquement le dernier thread qui supprime sa copie du pointeur partagé sera celui qui appelle delete (ce serait probablement le cas en pratique).

  3. Non, ils ne le font pas, l'objet qui y est stocké peut être modifié simultanément par plusieurs threads.

EDIT: Léger suivi, si vous voulez avoir une idée du fonctionnement des pointeurs partagés en général, vous voudrez peut-être consulter la boost::shared_ptrsource: http://www.boost.org/doc/libs/1_37_0/boost/shared_ptr.hpp .

Rien de plus
la source
3
1. Lorsque vous dites "'shared_ptrs', utilisez des incréments / décrémentations atomiques d'une valeur de comptage de référence." Voulez-vous dire qu'ils n'utilisent aucun verrou interne pour l'incrémentation / décrémentation atomique, qu'est-ce que le contexte change? Dans un langage simple, plusieurs threads peuvent-ils incrémenter / décrémenter le nombre de références sans utiliser de verrouillage? Un incrément atomique est effectué par des instructions spéciales atomic_test_and_swap / atomic_test_and_increment?
rahul.deshmukhpatil
@rahul le compilateur est libre d'utiliser un mutex / lock, mais la plupart des bons compilateurs n'utiliseront pas de mutex / lock sur les plates-formes où cela peut être fait sans verrouillage.
Bernard
@Bernard: voulez-vous dire que cela dépend de l'implémentation de "compilers std lib shared_ptr" pour la plateforme?
rahul.deshmukhpatil
2
Oui. D'après ce que j'ai compris, la norme ne dit pas qu'elle doit être sans verrou. Mais dans les derniers GCC et MSVC, il est sans verrouillage sur le matériel Intel x86, et je pense que d'autres bons compilateurs sont susceptibles de faire de même lorsque le matériel le prend en charge.
Bernard
18

std::shared_ptr n'est pas thread-safe.

Un pointeur partagé est une paire de deux pointeurs, un vers l'objet et un vers un bloc de contrôle (tenant le compteur de référence, des liens vers des pointeurs faibles ...).

Il peut y avoir plusieurs std :: shared_ptr et chaque fois qu'ils accèdent au bloc de contrôle pour changer le compteur de référence, il est thread-safe mais std::shared_ptrlui-même n'est PAS thread-safe ou atomique.

Si vous affectez un nouvel objet à un std::shared_ptrmoment où un autre thread l'utilise, il peut se retrouver avec le nouveau pointeur d'objet mais toujours en utilisant un pointeur vers le bloc de contrôle de l'ancien objet => CRASH.

Lothar
la source
4
Nous pourrions dire qu'une seule std::shared_ptrinstance n'est pas thread-safe. De la référence std :: shared_ptr:If multiple threads of execution access the same shared_ptr without synchronization and any of those accesses uses a non-const member function of shared_ptr then a data race will occur;
JKovalsky
Cela pourrait être mieux formulé. Une std::shared_ptr<T>instance est garantie thread-safe lorsqu'elle est toujours utilisée par valeur (copiée / déplacée) à travers les limites de thread. Toutes les autres utilisations std::shared_ptr<T>&sont dangereuses au
delà des