Ce article par Jeff supphing stipule que Le motif de verrouillage à double vérification (DCLP) est fixé en C ++ 11. L'exemple classique utilisé pour ce modèle est le modèle singleton, mais j'arrive à avoir un cas d'utilisation différent et je manque toujours d'expérience dans la manipulation "atomique <> armes" - peut-être que quelqu'un ici peut m'aider.
est la suivante Pièce de code Une implémentation correcte DCLP comme décrit par Jeff sous " à l'aide de C ++ 11 atomiques cohérents séquentiellement "? p>
3 Réponses :
Selon votre source, je pense que vous devez toujours ajouter des clôtures de fil avant le premier test et après le deuxième test.
std::shared_ptr<B> data; std::mutex mutex; void detach() { std::atomic_thread_fence(std::memory_order_acquire); if (data.use_count() > 1) { auto lock = std::lock_guard<std::mutex>{mutex}; if (data.use_count() > 1) { std::atomic_thread_fence(std::memory_order_release); data = std::make_shared<B>(*data); } } }
Je pense std :: partagé_ptr :: user_count () code> est équivalent à un appel interne à
std :: atomique
@HAUKEHEIBEL Je ne sais pas, je n'ai trouvé aucune référence qui soit soutenue ni contredire que. As-tu ?
@Nielk Que se passe-t-il si après le premier si code>, un autre thread favorise un
faibly_ptr code> à
data code> à un
partagé_ptr code>?
Non, ce n'est pas une implémentation correcte de DCLP.
Le truc est que votre suppose ces opérations ont été exécutés: p> alors vous avez la mise en page suivante ( data.use_count ()> 1 code>
B avec le nombre de références code>), qui peut être supprimé (non référencé) dans une partie protégée mutex. Toute sorte de clôtures de mémoire ne peut y aider là-bas. P>
affaiblé_ptr code> ne figure pas ici): p>
/* Thread 1 */ /* Thread 2 */ /* Thread 3 */
Enter detach() Enter detach()
Found `data.use_count()` > 1
Enter critical section
Found `data.use_count()` > 1
Dereference `data`,
found old object.
Unreference old `data`,
`use_count` becomes 1
Delete other shared_ptr,
old object is deleted
Assign new object to `data`
Access old object
(for check `use_count`)
!! But object is freed !!
Tous les joueurs, c'est-à-dire Data CODE>,
MUTEX CODE> et
DÉTACH () CODE> sont censés être membres d'une classe. Ainsi, il n'y a aucun moyen que
data code> soit supprimé si détacher () est appelé. Je vais aborder cette question en modifiant la question.
Il n'y a aucun moyen, ces données sont supprimées si détachez () est appelée code>. Je veux dire, objet référencé par i>
données code> est non fermé, car vous attribuez une nouvelle valeur à
Data code>. Donc, data.use_count () peut accéder à l'objet, tandis que Shared_PTR ne le pointe pas. C'est une mauvaise utilisation de Shared_PTR et peut conduire à un accès à la mémoire libérée, si d'autres Shared_Ptr's, indiqués sur cet objet sont supprimés au même moment.
Si je ne me trompe pas, votre scénario n'est possible que si le fil 1 ou le fil 2 travaux sur une référence à FOO - une référence appartenant à un autre fil. Votre exemple suppose en outre que le fil de possession de FOO le tue pendant que le fil tenant la référence y travaille toujours. Si tel est le cas, alors quelque chose est de toute façon substantiellement brisé.
Si votre référence code> Data CODE> appartient et accédée uniquement par un seul thread, pourquoi vous utilisez le verrouillage? Voir le point 2 à la fin de ma réponse.
Je n'ai jamais dit que l'objet ne doit pas être utilisé par plusieurs threads, mais il ne devrait certainement pas être supprimé alors que l'un des threads y accède - il n'y a aucune protection contre ce cas d'utilisation. Pour être plus clair, le cas d'utilisation que je vis avec foo code> est une implémentation de copie-écriture qui tire parti du fait que
std :: partagé_ptr code> est déjà référence compté dans un fil de fil de sécurité. Le grand piège est le logement de fil de fil de sécurité sur des appels de fonctions membres non constitutifs sur
foo code>. Un problème sans le mutex est par exemple créer plusieurs copies où on aurait été suffisant.
Essayons de clarifier les problèmes restants. Pour exclure le scénario thread 3 i> Supposons que foo code> n'est jamais détruit alors qu'il est utilisé par deux threads. Éliminons également votre point 2. - Comme vous l'avez déjà observé, toute la question serait discutable dans ce scénario. En ce qui concerne votre explication, il existe un malentendu du code. Vérification extérieure B> Ne jamais accéder à
B code>,
user_count () code> est une fonction de membre de
partagé_ptr code>.
Je ne reçois pas le point 1 puisque l'un ou l'autre des threads multiples, il suffit de lire B code> qui est bien ou au moins un fil tente d'écrire sur
B code> (c.-à-d. Utiliser_count ()> 1 dans un multi Scénario organisé). Dans ce dernier cas,
se détach () code> garantit que le thread écrit à un
B code> car après avoir appelé
détach () code>,
use_count () == 1 code>.
J'ai mis à jour la réponse avec l'explication pourquoi .uuse_count () accède à l'objet code> (de type
B avec le nombre de références code>). Si vous souhaitez empêcher cet objet de la suppression pendant
détach () code> appel, qui change éventuellement des données code>, vous devez avoir un autre i>
Shared_ptr < / code> pointé dessus. Mais avoir un autre
partagé_ptr code> vous forcer
data.use_count ()> 1 code> est toujours vrai. Les points à la fin de ma réponse décrivent les accès à B uniquement via
données code>. Accès via autre
Shared_PTR code> S ne sont pas pris en compte. J'espère que ces explications aideront.
Merci d'avoir essayé de clarifier mes questions. Ma compréhension actuelle est que vous décrivez le problème général des références pendantes. Ce problème n'est pas satisfait de "toute sorte de clôtures de mémoire" comme indiqué dans la réponse et donc inhérente à toute utilisation d'un mutex au sein d'une fonction membre - il n'est pas spécifique de DCLP. La réponse indique un problème de programmation valide et général. Ma question est vraiment si'use_Count () 'peut provoquer une course avec la mission de déplacement ou la construction de la construction d'A Hered_ptr'.
Oui, votre compréhension est correcte: le problème général de votre code est des références dangereuses. data.use_count () code> n'est pas couru avec
data1 = données; code> Copier la construction. Accès simultané à
data.use_count () code> et n'importe quelle méthode, modifiant
data code> (E.G.,
data = DATA1; CODE> DATA1; Il peut être traité comme une course entre l'accès à la mémoire et libérer cette mémoire.
Je pense que nous sommes sur la bonne voie. Pouvons-nous convenir que nous ignorons le cas d'utilisation où le user_count () code> tombe sur 0? Si nous ne le faisons pas que ce code ne peut pas casser
{lock_guard
Lock_Guard code> appelle
std :: mutex :: déverrouiller () code> sur
m_mutex code> qui est parti si la classe possède
m_mutex code> est supprimé pendant que certains appels de threads
autre_code () code>. Mon point est que la langue n'offre aucune aide pour ce scénario et que nous devrions l'ignorer.
Vous mal comprendre user_count code> Protection: il protège l'objet B plus le compteur lui-même d'être libéré. Objet
FOO CODE> n'est pas supprimé automatiquement lorsque
user_count code> gouttes à 0, donc
mutex code> est vivant.
Je comprends use_count () code> assez bien. Je ne connais pas d'autre solution, mais de montrer un exemple de code qui démontre que votre scénario peut toujours planter: Ideone.com/tbyep (veuillez noter que vous devez tester localement car Ideone ne prend pas en charge la multi-threading). Si le fil 3 tue
foo code>, c'est le jeu. Le
partagé_ptr code> est privé et ne peut être détruit que via le destructeur
FOO code>.
Je pense enfin à attraper enfin ce que vous voulez: chaque objet foo code> est accessible à partir d'un seul thread, et si
foo foo1, foo2 = foo1; code>, alors un seul des
simultané foo1.detach () code> et
foo2.detach () code> devrait créer un nouvel objet B; autre devrait réutiliser l'objet actuel. Si tel est le cas, votre DCLP d'origine devient complètement correct après avoir fabriqué
mutex code> statique.
Ceci constitue une course de données si deux threads invoquent détacher code> sur la même instance de
FOO code> simultanément, car
std :: partagé_ptr :: user_count ( ) code> (une opération en lecture seule) fonctionnerait simultanément avec le
std :: Shared_ptr code> Opérateur d'affectation de déplacement (une opération de modification), qui est une course de données et donc une cause de comportement non défini. Si
FOO code> n'est jamais accessible simultanément, d'autre part, il n'y a pas de course de données, mais le
std :: mutex code> serait inutile dans votre exemple. La question est la suivante: comment code> data code> est-il partagé en premier lieu? Sans ce bit crucial d'informations, il est difficile de dire si le code est en sécurité, même si un
FOO code> n'est jamais utilisé simultanément. P>
J'ai initialement utilisé ce code std :: atomic_store (& data, std :: make_shared (* autre.data)); code> au lieu de l'affectation de déplacement. Si plusieurs threads créent des copies de
foo code> Vous avez probablement raison et que nous devons mettre en œuvre la construction, la copie et l'affectation de
foo code> tout en termes de
std :: atomic_load / stocker / stocker code>. Peut-être que quelqu'un d'autre peut confirmer cela avant de modifier la publication? @Kerreksb a suggéré que le «std: atomic_store» soit inutile mais je ne suis plus sûr du moins pas lorsque vous envisagez toute la classe.
Si vous gardez des données code> avec un mutex, vous n'avez pas besoin du
atomic_store code>.
user_count code> est
const code>, donc par les règles de la bibliothèque générale, l'appelant simultanément ne provoque pas de course de données.
Notez que votre code est au mieux "le meilleur effort". La condition
data.use_count ()> 1 code> est pas i> corrigé par le mutex! Les copies existantes peuvent être détruites simultanément.
@Kerreksb j'ai entendu parler du
const code> Remarque dans les herbes Talk . Seriez-vous vraiment aller jusqu'à supposer que cette règle est généralement mise en œuvre? Merci pour l'indice sur le
atomic_store code> - j'ai négligé cela.
Eh bien, [res.on.data.races] / 2 exige que les fonctions de Cons-Membres ne modifient pas les objets. Cependant, cela est un peu flou, car la copie de
données code> change clairement le nombre d'utilisations. Mais le constructeur de copie prend une valeur de const, alors je suppose que cela signifie que cela ne constitue pas une modification pertinente pour les courses de données. En pratique, je m'attends à ce que cela soit gratuit, mais ce n'est pas tout à fait clair de la norme autant que je puisse dire.
Ah, tout va bien. Tout d'abord, le paragraphe cité indique essentiellement que le constructeur de copie ne "modifie pas" l'argument (car c'est Const). Il y a une clarification dans [util.smartptr.shared] / 4: "Aux fins de déterminer la présence d'une course de données, les fonctions membres doivent accéder et modifier uniquement le
partagé_ptr code> et
faibles_ptr code > Objets eux-mêmes et non des objets qu'ils se réfèrent. Modifications de
use_count () code> ne reflètent pas les modifications pouvant introduire des courses de données. "
@Kerreksb: Appeler une fonction
const code> concurrente avec une fonction non-
const code> (telle que
opérateur = code> comme c'est le cas ici) peut < / i> causer une course de données
@Chrisdodd: Bien sûr, mais ce n'est pas ce qui se passe ici. L'accès modifiant est sérialisé via le mutex. L'aspect intéressant est que même s'il ne s'agissait jamais des vues const de
données code>, la valeur de
data.use_count () code> peut toujours changer.