L'implémentation suivante, utilisant l'initialisation différée, de Singleton
(Meyers 'Singleton) est-elle sûre?
static Singleton& instance()
{
static Singleton s;
return s;
}
Sinon, pourquoi et comment le rendre sûr pour les threads?
L'implémentation suivante, utilisant l'initialisation différée, de Singleton
(Meyers 'Singleton) est-elle sûre?
static Singleton& instance()
{
static Singleton s;
return s;
}
Sinon, pourquoi et comment le rendre sûr pour les threads?
Réponses:
En C ++ 11 , il est thread-safe. Selon la norme , §6.7 [stmt.dcl] p4
:
Si le contrôle entre la déclaration simultanément pendant l'initialisation de la variable, l' exécution simultanée doit attendre la fin de l'initialisation.
La prise en charge de GCC et VS pour la fonctionnalité ( Initialisation et Destruction Dynamiques avec Concurrence , également connue sous le nom de Magic Statics sur MSDN ) est la suivante:
Merci à @Mankarse et @olen_gam pour leurs commentaires.
En C ++ 03 , ce code n'était pas thread-safe. Il existe un article de Meyers intitulé "C ++ and the Perils of Double-Checked Locking" qui traite des implémentations thread-safe du modèle, et la conclusion est, plus ou moins, que (en C ++ 03) un verrouillage complet autour de la méthode d'instanciation est fondamentalement le moyen le plus simple d'assurer une concurrence appropriée sur toutes les plates-formes, tandis que la plupart des formes de variantes de modèle de verrouillage vérifiées deux fois peuvent souffrir de conditions de concurrence sur certaines architectures , à moins que les instructions ne soient entrelacées avec des barrières de mémoire placées stratégiquement.
Pour répondre à votre question sur la raison pour laquelle ce n'est pas threadsafe, ce n'est pas parce que le premier appel à instance()
doit appeler le constructeur pour Singleton s
. Pour être threadsafe, cela devrait se produire dans une section critique, mais il n'y a pas d'exigence dans la norme qu'une section critique soit prise (la norme à ce jour est complètement silencieuse sur les threads). Les compilateurs implémentent souvent cela en utilisant une simple vérification et incrémentation d'un booléen statique - mais pas dans une section critique. Quelque chose comme le pseudocode suivant:
static Singleton& instance()
{
static bool initialized = false;
static char s[sizeof( Singleton)];
if (!initialized) {
initialized = true;
new( &s) Singleton(); // call placement new on s to construct it
}
return (*(reinterpret_cast<Singleton*>( &s)));
}
Voici donc un simple Singleton thread-safe (pour Windows). Il utilise une simple enveloppe de classe pour l'objet de Windows CRITICAL_SECTION afin que nous puissions avoir le compilateur initialiser automatiquement l' CRITICAL_SECTION
avant main()
est appelée. Idéalement, une véritable classe de section critique RAII serait utilisée pour traiter les exceptions qui pourraient se produire lorsque la section critique est conservée, mais cela dépasse le cadre de cette réponse.
L'opération fondamentale est que lorsqu'une instance de Singleton
est demandée, un verrou est pris, le singleton est créé s'il le faut, puis le verrou est libéré et la référence Singleton renvoyée.
#include <windows.h>
class CritSection : public CRITICAL_SECTION
{
public:
CritSection() {
InitializeCriticalSection( this);
}
~CritSection() {
DeleteCriticalSection( this);
}
private:
// disable copy and assignment of CritSection
CritSection( CritSection const&);
CritSection& operator=( CritSection const&);
};
class Singleton
{
public:
static Singleton& instance();
private:
// don't allow public construct/destruct
Singleton();
~Singleton();
// disable copy & assignment
Singleton( Singleton const&);
Singleton& operator=( Singleton const&);
static CritSection instance_lock;
};
CritSection Singleton::instance_lock; // definition for Singleton's lock
// it's initialized before main() is called
Singleton::Singleton()
{
}
Singleton& Singleton::instance()
{
// check to see if we need to create the Singleton
EnterCriticalSection( &instance_lock);
static Singleton s;
LeaveCriticalSection( &instance_lock);
return s;
}
Mec - c'est beaucoup de merde pour "faire un monde meilleur".
Les principaux inconvénients de cette implémentation (si je n'ai pas laissé passer certains bogues) sont:
new Singleton()
jette, le verrou ne sera pas libéré. Cela peut être résolu en utilisant un véritable objet de verrouillage RAII au lieu du simple objet que j'ai ici. Cela peut également aider à rendre les choses portables si vous utilisez quelque chose comme Boost pour fournir un wrapper indépendant de la plate-forme pour le verrou.main()
été appelée - si vous l'appelez avant (comme dans l'initialisation d'un objet statique), les choses peuvent ne pas fonctionner car le CRITICAL_SECTION
peut ne pas être initialisé.new Singleton()
jette?
new Singleton()
jette il y a certainement un problème avec le verrou. Une classe de verrouillage RAII appropriée doit être utilisée, quelque chose comme lock_guard
Boost. Je voulais que l'exemple soit plus ou moins autonome, et c'était déjà un peu un monstre, alors j'ai laissé la sécurité des exceptions (mais je l'ai appelé). Peut-être que je devrais résoudre ce problème pour que ce code ne soit pas copié-collé dans un endroit inapproprié.
En regardant le standard suivant (section 6.7.4), il explique comment l'initialisation locale statique est thread-safe. Ainsi, une fois que cette section de la norme sera largement implémentée, Meyer's Singleton sera l'implémentation préférée.
Je suis déjà en désaccord avec de nombreuses réponses. La plupart des compilateurs implémentent déjà l'initialisation statique de cette façon. La seule exception notable est Microsoft Visual Studio.
L'implémentation suivante est-elle [...] thread-safe?
Sur la plupart des plates-formes, ce n'est pas thread-safe. (Ajoutez la clause de non-responsabilité habituelle expliquant que le standard C ++ ne connaît pas les threads, donc, légalement, il ne dit pas si c'est le cas ou non.)
Sinon, pourquoi [...]?
La raison pour laquelle ce n'est pas le cas est que rien n'empêche plus d'un thread d'exécuter simultanément s
'constructeur.
comment le rendre sûr pour les threads?
"C ++ et les périls du double verrouillage" par Scott Meyers et Andrei Alexandrescu est un très bon traité sur le sujet des singletons thread-safe.
Comme MSalters l'a dit: cela dépend de l'implémentation C ++ que vous utilisez. Consultez la documentation. Quant à l'autre question: "Si non, pourquoi?" - Le standard C ++ ne mentionne encore rien sur les threads. Mais la prochaine version C ++ est consciente des threads et indique explicitement que l'initialisation des locals statiques est thread-safe. Si deux threads appellent une telle fonction, un thread effectuera une initialisation tandis que l'autre bloquera et attendra qu'elle se termine.