C'est un peu une question en deux parties, tout sur l'atomicité de std::shared_ptr
:
1.
Autant que je sache, std::shared_ptr
c'est le seul pointeur intelligent <memory>
qui soit atomique. Je me demande s'il existe une version non atomique de std::shared_ptr
disponible (je ne vois rien dedans <memory>
, donc je suis également ouvert aux suggestions en dehors du standard, comme celles de Boost). Je sais que boost::shared_ptr
c'est aussi atomique (si ce BOOST_SP_DISABLE_THREADS
n'est pas défini), mais peut-être qu'il y a une autre alternative? Je recherche quelque chose qui a la même sémantique que std::shared_ptr
, mais sans l'atomicité.
2. Je comprends pourquoi std::shared_ptr
est atomique; c'est plutôt sympa. Cependant, ce n'est pas bien pour toutes les situations, et C ++ a toujours eu pour mantra "ne payez que pour ce que vous utilisez". Si je n'utilise pas plusieurs threads, ou si j'utilise plusieurs threads mais que je ne partage pas la propriété du pointeur entre les threads, un pointeur intelligent atomique est excessif. Ma deuxième question est pourquoi une version non atomique de std::shared_ptr
C ++ 11 n'a pas été fournie ? (en supposant qu'il y ait un pourquoi ) (si la réponse est simplement "une version non atomique n'a tout simplement jamais été envisagée" ou "personne n'a jamais demandé une version non atomique" c'est très bien!).
Avec la question n ° 2, je me demande si quelqu'un a déjà proposé une version non atomique de shared_ptr
(soit à Boost, soit au comité des normes) (non pas pour remplacer la version atomique de shared_ptr
, mais pour coexister avec elle) et elle a été abattue pour un Raison spécifique.
la source
shared_ptr
été un ralentissement important en raison de son atomicité, et la définition aBOOST_DISABLE_THREADS
fait une différence notable (je ne sais pas sistd::shared_ptr
cela aurait eu le même coût que celaboost::shared_ptr
).shared_ptr
n'utilise pas d'opérations atomiques pour le refcount. Voir (2) sur gcc.gnu.org/ml/libstdc++/2007-10/msg00180.html pour un correctif à GCC pour permettre à l'implémentation non atomique d'être utilisée même dans les applications multithread, pour lesshared_ptr
objets qui ne sont pas partagés entre fils. Je suis assis sur ce patch depuis des années mais j'envisage enfin de le valider pour GCC 4.9Réponses:
Non fourni par la norme. Il peut bien y en avoir un fourni par une bibliothèque «tierce». En effet, avant C ++ 11 et avant Boost, il semblait que tout le monde écrivait sa propre référence comptant un pointeur intelligent (moi y compris).
Cette question a été débattue lors de la réunion de Rapperswil en 2010. Le sujet a été introduit par le commentaire n ° 20 d'un organisme national de la Suisse. Il y avait des arguments forts des deux côtés du débat, y compris ceux que vous fournissez dans votre question. Cependant, à la fin de la discussion, le vote était massivement (mais pas unanime) contre l'ajout d'une version non synchronisée (non atomique) de
shared_ptr
.Les arguments contre comprenaient:
Le code écrit avec shared_ptr non synchronisé peut finir par être utilisé dans du code threadé plus tard, ce qui entraîne des problèmes difficiles à déboguer sans avertissement.
Avoir un shared_ptr "universel" qui est le "sens unique" du trafic dans le comptage des références présente des avantages: D'après la proposition d'origine :
Le coût des atomes, bien que non nul, n'est pas écrasant. Le coût est atténué par l'utilisation de la construction de mouvement et de l'affectation de mouvement qui n'ont pas besoin d'utiliser des opérations atomiques. De telles opérations sont couramment utilisées pour l'
vector<shared_ptr<T>>
effacement et l'insertion.Rien n'empêche les gens d'écrire leur propre pointeur intelligent compté par référence non atomique si c'est vraiment ce qu'ils veulent faire.
Le dernier mot du LWG de Rapperswil ce jour-là était:
la source
Has the same object type regardless of features used, greatly facilitating interoperability between libraries, including third-party libraries.
c'est un raisonnement extrêmement étrange. Les bibliothèques tierces fourniront de toute façon leurs propres types, alors pourquoi serait-il important de les fournir sous la forme std :: shared_ptr <CustomType>, std :: non_atomic_shared_ptr <CustomType>, etc.? vous devrez toujours adapter votre code à ce que la bibliothèque renvoie de toute façonstd::shared_ptr<std::string>
quelque part. Si la bibliothèque de quelqu'un d'autre prend également ce type, les appelants peuvent nous transmettre les mêmes chaînes sans les inconvénients ou les frais généraux liés à la conversion entre différentes représentations, et c'est une petite victoire pour tout le monde.Howard a déjà bien répondu à la question, et Nicol a souligné les avantages d'avoir un seul type de pointeur partagé standard, plutôt que beaucoup de types incompatibles.
Bien que je sois entièrement d'accord avec la décision du comité, je pense qu'il y a un certain avantage à utiliser un
shared_ptr
type non synchronisé dans des cas particuliers . J'ai donc étudié le sujet à quelques reprises.Avec GCC lorsque votre programme n'utilise pas plusieurs threads shared_ptr n'utilise pas d'opérations atomiques pour le refcount. Cela se fait en mettant à jour le nombre de références via des fonctions wrapper qui détectent si le programme est multithread (sous GNU / Linux, cela se fait simplement en détectant si le programme est lié à
libpthread.so
) et en les distribuant aux opérations atomiques ou non atomiques en conséquence.J'ai réalisé il y a de nombreuses années que parce que GCC
shared_ptr<T>
est implémenté en termes de__shared_ptr<T, _LockPolicy>
classe de base , il est possible d'utiliser la classe de base avec la politique de verrouillage monothread même dans le code multithread, en utilisant explicitement__shared_ptr<T, __gnu_cxx::_S_single>
. Malheureusement, comme ce n'était pas un cas d'utilisation prévu, cela ne fonctionnait pas de manière optimale avant GCC 4.9, et certaines opérations utilisaient toujours les fonctions wrapper et étaient donc distribuées aux opérations atomiques même si vous avez explicitement demandé la_S_single
politique. Voir le point (2) sur http://gcc.gnu.org/ml/libstdc++/2007-10/msg00180.htmlpour plus de détails et un correctif pour GCC pour permettre à l'implémentation non atomique d'être utilisée même dans les applications multithread. Je me suis assis sur ce patch pendant des années, mais je l'ai finalement validé pour GCC 4.9, qui vous permet d'utiliser un modèle d'alias comme celui-ci pour définir un type de pointeur partagé qui n'est pas thread-safe, mais qui est légèrement plus rapide:template<typename T> using shared_ptr_unsynchronized = std::__shared_ptr<T, __gnu_cxx::_S_single>;
Ce type ne serait pas interopérable avec
std::shared_ptr<T>
et ne serait sûr à utiliser que s'il est garanti que lesshared_ptr_unsynchronized
objets ne seront jamais partagés entre les threads sans une synchronisation supplémentaire fournie par l'utilisateur.C'est bien sûr complètement non portable, mais parfois c'est OK. Avec les bons hacks de préprocesseur, votre code fonctionnera toujours bien avec d'autres implémentations si
shared_ptr_unsynchronized<T>
c'est un alias pourshared_ptr<T>
, ce serait juste un peu plus rapide avec GCC.Si vous utilisez un GCC antérieur à 4.9, vous pouvez l'utiliser en ajoutant les
_Sp_counted_base<_S_single>
spécialisations explicites à votre propre code (et en vous assurant que personne n'instancie jamais__shared_ptr<T, _S_single>
sans inclure les spécialisations, pour éviter les violations ODR.) L'ajout de telles spécialisations destd
types est techniquement indéfini, mais le ferait travailler dans la pratique, car dans ce cas, il n'y a aucune différence entre moi ajoutant les spécialisations à GCC ou vous les ajoutant à votre propre code.la source
std::shared_ptr
,std::__shared_ptr
,__default_lock_policy
et tel. Cette réponse a confirmé ce que j'ai compris du code.On pourrait tout aussi facilement se demander pourquoi il n'y a pas de pointeur intrusif, ou un certain nombre d'autres variantes possibles de pointeurs partagés que l'on pourrait avoir.
La conception de
shared_ptr
, transmise par Boost, a été de créer une lingua-franca standard minimale de pointeurs intelligents. En règle générale, vous pouvez simplement retirer cela du mur et l'utiliser. C'est quelque chose qui serait utilisé en général, dans une grande variété d'applications. Vous pouvez le mettre dans une interface, et il y a de fortes chances que de bonnes personnes soient disposées à l'utiliser.Le filetage ne fera que devenir plus répandu à l'avenir. En effet, au fil du temps, le filetage sera généralement l'un des principaux moyens d'atteindre les performances. Exiger que le pointeur intelligent de base fasse le strict minimum nécessaire pour prendre en charge le threading facilite cette réalité.
Jeter une demi-douzaine de pointeurs intelligents avec des variations mineures entre eux dans la norme, ou pire encore un pointeur intelligent basé sur une politique, aurait été terrible. Chacun choisirait le pointeur qu'il préfère et renoncerait à tous les autres. Personne ne pourrait communiquer avec qui que ce soit. Ce serait comme les situations actuelles avec les chaînes C ++, où chacun a son propre type. Seulement bien pire, car l'interopérabilité avec des chaînes est beaucoup plus facile que l'interopérabilité entre les classes de pointeurs intelligents.
Boost, et par extension le comité, a choisi un pointeur intelligent spécifique à utiliser. Il offrait un bon équilibre des fonctionnalités et était largement et couramment utilisé dans la pratique.
std::vector
a quelques inefficacités par rapport aux tableaux nus dans certains cas aussi. Il a certaines limites; certaines utilisations veulent vraiment avoir une limite stricte sur la taille de avector
, sans utiliser d'allocateur de lancement. Cependant, le comité n'a pas conçuvector
pour être tout pour tout le monde. Il a été conçu pour être un bon défaut pour la plupart des applications. Ceux pour qui cela ne peut pas fonctionner peuvent simplement écrire une alternative qui répond à leurs besoins.Tout comme vous pouvez pour un pointeur intelligent si
shared_ptr
l'atomicité de est un fardeau. Là encore, on pourrait aussi envisager de ne pas trop les copier.la source
Je prépare une conférence sur shared_ptr au travail. J'ai utilisé un boost shared_ptr modifié avec éviter malloc séparé (comme ce que make_shared peut faire) et un paramètre de modèle pour la politique de verrouillage comme shared_ptr_unsynchronized mentionné ci-dessus. J'utilise le programme depuis
http://flyingfrogblog.blogspot.hk/2011/01/boosts-sharedptr-up-to-10-slower-than.html
comme test, après avoir nettoyé les copies shared_ptr inutiles. Le programme utilise uniquement le thread principal et l'argument de test est affiché. L'env de test est un notebook exécutant linuxmint 14. Voici le temps pris en secondes:
Seule la version 'std' utilise -std = cxx11, et -pthread bascule probablement lock_policy dans la classe g ++ __shared_ptr.
À partir de ces chiffres, je vois l'impact des instructions atomiques sur l'optimisation du code. Le scénario de test n'utilise aucun conteneur C ++, mais
vector<shared_ptr<some_small_POD>>
risque de souffrir si l'objet n'a pas besoin de la protection des threads. Boost souffre moins probablement parce que le malloc supplémentaire limite la quantité d'inlining et d'optimisation du code.Je n'ai pas encore trouvé de machine avec suffisamment de cœurs pour tester l'évolutivité des instructions atomiques, mais utiliser std :: shared_ptr uniquement lorsque cela est nécessaire est probablement mieux.
la source
Boost fournit un système
shared_ptr
non atomique. Il s'appellelocal_shared_ptr
et se trouve dans la bibliothèque de pointeurs intelligents de boost.la source
shared_ptr
un comptoir de toute façon, même si c'est local? Ou voulez-vous dire qu'il y a un autre problème avec ça? La documentation dit que la seule différence est que ce n'est pas atomique.local_shared_ptr
et qu'ilsshared_ptr
sont identiques sauf pour atomic. Je suis vraiment intéressé à savoir si ce que vous dites est vrai parce que je l'utiliselocal_shared_ptr
dans des applications qui nécessitent des performances élevées.