L'implémentation suivante, utilisant l'initialisation différée, du thread 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?
Dans C++ 11 , il est thread-safe. Selon le standard , §6.7 [stmt.dcl] p4
:
Si le contrôle entre simultanément dans la déclaration pendant l'initialisation de la variable, l'exécution simultanée attend la fin de l'initialisation.
La prise en charge de GCC et VS pour la fonctionnalité ( Initialisation dynamique et destruction avec concurrence , également connue sous le nom de Magic Statics on MSDN ) est la suivante:
Merci à @Mankarse et @olen_gam pour leurs commentaires.
Dans C++ , ce code n'était pas sûr pour les threads. Il y a 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 ) le 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 à double vérification peuvent souffrir de conditions de concurrence sur certaines architectures , sauf si les instructions sont entrelacées avec place stratégiquement des barrières de mémoire.
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 sûr des threads, cela devrait se produire dans une section critique, mais il n'y a aucune 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 un simple wrapper de classe pour l'objet Windows CRITICAL_SECTION afin que le compilateur puisse initialiser automatiquement le CRITICAL_SECTION
Avant d'appeler main()
. Idéalement, une véritable classe de section critique RAII serait utilisée qui pourrait traiter les exceptions qui pourraient se produire lorsque la section critique est maintenue, 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 retourné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;
}
Homme - c'est beaucoup de merde pour "faire un meilleur monde".
Les principaux inconvénients de cette implémentation (si je n'ai pas laissé passer quelques bugs) est:
new Singleton()
lance, 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 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()
- si vous l'appelez avant (comme dans l'initialisation d'un objet statique), les choses pourraient ne pas fonctionner car le CRITICAL_SECTION
pourrait ne pas être initialisé.En examinant la norme suivante (section 6.7.4), il explique comment l'initialisation locale statique est sûre pour les threads. Ainsi, une fois cette section de la norme largement mise en œuvre, le Singleton de Meyer sera la mise en œuvre 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.
La bonne réponse dépend de votre compilateur. Il peut décider de rendre sûr pour les threads; ce n'est pas "naturellement" sûr pour les threads.
Le thread d'implémentation suivant est-il sûr?
Sur la plupart des plateformes, ce n'est pas thread-safe. (Ajoutez l'avertissement habituel 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 n'est pas que rien n'empêche plus d'un thread d'exécuter simultanément le constructeur s
'.
comment le rendre sûr pour les threads?
"C++ and the Perils of Double-Checked Locking" par Scott Meyers et Andrei Alexandrescu est un assez bon traité sur le sujet des singletons thread-safe.
Comme l'a dit MSalters: Cela dépend de l'implémentation C++ que vous utilisez. Consultez la documentation. Quant à l'autre question: "Sinon, pourquoi?" - La norme C++ ne mentionne encore rien sur les threads. Mais la prochaine version C++ est consciente des threads et indique explicitement que l'initialisation des sections locales statiques est thread-safe. Si deux threads appellent une telle fonction, un thread effectuera une initialisation tandis que l'autre bloquera et attendra sa fin.