std:: sécurité des threads PTR partagés

J'ai lu ça

" plusieurs threads peuvent simultanément lire et écrire différents shared_ptr objets, même lorsque les objets sont des copies qui partagent propriété."(MSDN: sécurité des threads dans la bibliothèque C++ standard)

Cela signifie-t-il que la modification de l'objet shared_ptr est sûre ?
Pour une instance, le code suivant est-il considéré comme sûr:

shared_ptr<myClass> global = make_shared<myClass>();
...

//In thread 1
shared_ptr<myClass> private = global;
...

//In thread 2
global = make_shared<myClass>();
...

Puis-je être sûr dans ce cas que le thread 1 private aura la valeur d'origine de global ou la nouvelle valeur quel thread 2 assigné mais de toute façon il aura un shared_ptr valide à myClass?

= = modifier = =
Juste pour expliquer ma motivation. Je veux avoir un pointeur partagé pour contenir ma configuration et j'ai un pool de threads pour gérer les requêtes.
donc global est la configuration globale.
thread 1 prend la configuration actuelle car elle commence à gérer une requête.
thread 2 met à jour la configuration. (ne s'applique qu'aux demandes futures)

Si c'est du travail, je peux mettre à jour le configuration de cette façon sans le casser au milieu d'une gestion de requête.

37
demandé sur Roee Gavirel 2013-01-23 19:09:04

4 réponses

Ce que vous lisez ne signifie pas ce que vous pensez que cela signifie. Tout d'abord, essayez la page msdn pour shared_ptr lui-même.

Faites défiler vers le bas dans la section "Remarques" et vous arriverez à la viande de la question. Fondamentalement, un shared_ptr<> pointe vers un "bloc de contrôle" qui est la façon dont il garde la trace du nombre d'objets shared_ptr<> pointant réellement vers l'objet "réel". Donc, quand vous faites ceci:

shared_ptr<int> ptr1 = make_shared<int>();

Alors qu'il n'y a que 1 appel pour allouer de la mémoire ici via make_shared, Il y en a deux blocs "logiques" que vous ne devriez pas traiter de la même manière. L'un est le int, qui stocke la valeur réelle, et l'autre est le bloc de contrôle, qui stocke toutes les shared_ptr<> "magique" qui le fait fonctionner.

C'est seulement le bloc de contrôle lui-même qui est thread-safe.

Je mets cela sur sa propre ligne pour l'accent. Le contenu du shared_ptr ne sont pas thread-safe, ni écrit dans la même instance shared_ptr. Voici quelque chose pour démontrer ce que je moyenne:

// In main()
shared_ptr<myClass> global_instance = make_shared<myClass>();
// (launch all other threads AFTER global_instance is fully constructed)

//In thread 1
shared_ptr<myClass> local_instance = global_instance;

C'est bien, en fait vous pouvez le faire dans tous les threads autant que vous le souhaitez. Et puis quand local_instance est détruit (en sortant de la portée), il est également thread-safe. Quelqu'un peut accéder à global_instance et cela ne fera aucune différence. L'extrait que vous avez tiré de msdn signifie essentiellement que "l'accès au bloc de contrôle est sécurisé par les threads", de sorte que d'autres instances shared_ptr<> peuvent être créées et détruites sur différents threads autant que nécessaire.

//In thread 1
local_instance = make_shared<myClass>();

, Ce qui est correct. Il sera affecte l'objet global_instance, mais seulement indirectement. Le bloc de contrôle vers lequel il pointe sera décrémenté, mais fait de manière sûre pour les threads. local_instance ne pointera plus vers le même objet (ou bloc de contrôle) que global_instance.

//In thread 2
global_instance = make_shared<myClass>();

Ce n'est certainement pas bien si global_instance est accessible à partir d'autres threads (ce que vous dites faire). Il a besoin d'un verrou si vous faites cela parce que vous écrivez partout où global_instance vit, pas seulement en lisant. Donc, écrire sur un objet à partir de plusieurs threads est mauvais à moins que ce soit vous l'avez gardé à travers une serrure. Vous pouvez donc lire à partir de global_instance l'objet en lui attribuant de nouveaux objets shared_ptr<> mais vous ne pouvez pas y écrire.

// In thread 3
*global_instance = 3;
int a = *global_instance;

// In thread 4
*global_instance = 7;

, La valeur de a n'est pas défini. Il pourrait être 7, ou il pourrait être 3, ou il pourrait être autre chose ainsi. La sécurité des threads des instances shared_ptr<> ne s'applique qu'à la gestion des instances shared_ptr<> initialisées les unes par rapport aux autres, pas à ce qu'elles pointent.

Pour souligner ce que je veux dire, regardez ce:

shared_ptr<int> global_instance = make_shared<int>(0);

void thread_fcn();

int main(int argc, char** argv)
{
    thread thread1(thread_fcn);
    thread thread2(thread_fcn);
    ...
    thread thread10(thread_fcn);

    chrono::milliseconds duration(10000);
    this_thread::sleep_for(duration);

    return;
}

void thread_fcn()
{
    // This is thread-safe and will work fine, though it's useless.  Many
    // short-lived pointers will be created and destroyed.
    for(int i = 0; i < 10000; i++)
    {
        shared_ptr<int> temp = global_instance;
    }

    // This is not thread-safe.  While all the threads are the same, the
    // "final" value of this is almost certainly NOT going to be
    // number_of_threads*10000 = 100,000.  It'll be something else.
    for(int i = 0; i < 10000; i++)
    {
        *global_instance = *global_instance + 1;
    }
}

Un shared_ptr<> est un mécanisme pour assurer que plusieurs objet propriétaires de garantir que l'objet est détruit, pas un mécanisme pour s'assurer de multiples threads peut accéder à un objet correctement. Vous avez toujours besoin d'un mécanisme de synchronisation séparé pour l'utiliser en toute sécurité dans plusieurs threads (comme std:: mutex ).

La meilleure façon D'y penser IMO est que shared_ptr<> s'assure que plusieurs copies pointant vers la même mémoire n'ont pas de problèmes de synchronisation pour lui, mais ne fait rien pour l'objet pointé. Le traiter comme ça.

71
répondu Kevin Anderson 2017-08-24 18:09:13

Pour ajouter à ce que Kevin a écrit, la spécification C++14 a un support supplémentaire pour l'accès atomique aux objets shared_ptr eux-mêmes:

20.8.2.6 shared_ptr accès atomique [util.smartptr.partager.atomique]

L'accès simultané à un objet shared_ptr à partir de plusieurs threads n'introduit pas de course de données si l'accès se fait exclusivement via les fonctions de cette section et que l'instance est passée comme premier argument.

Donc si vous le faites:

//In thread 1
shared_ptr<myClass> private = atomic_load(&global);
...

//In thread 2
atomic_store(&global, make_shared<myClass>());
...

Ce sera thread-safe.

21
répondu Chris Dodd 2016-03-02 04:50:02

Cela signifie que vous aurez un shared_ptr valide et un comptage de référence valide.

Vous décrivez une condition de concurrence entre 2 threads qui essaient de lire / assigner à la même variable.

Parce que c'est un comportement indéfini en général (cela n'a de sens que dans le contexte et le calendrier du programme individuel) shared_ptr ne gère pas cela.

4
répondu Yochai Timmer 2018-07-25 12:53:32

Les opérations de lecture ne sont pas soumises à des courses de données entre elles, il est donc prudent de partager la même instance de shared_ptr entre les threads tant que tous les threads utilisent les méthodes const uniquement (Cela inclut la création de copies). Dès qu'un thread utilise une méthode non-const (comme dans "pointez-le vers un autre objet"), une telle utilisation n'est plus sécurisée.

L'exemple OP n'est pas thread safe et nécessiterait l'utilisation de la charge atomique dans le thread 1. et magasin atomique dans le thread 2 (section 2.7.2.5 en C++11) pour le rendre sûr.

Le mot clé dans le texte MSDN est en effet différents objets shared_ptr , comme déjà indiqué dans les réponses précédentes.

2
répondu Leon 2016-06-03 22:59:06