L'implémentation de Meyers du thread Singleton pattern est-elle sûre?
L'implémentation suivante, en utilisant une initialisation paresseuse, du thread Singleton
(Singleton de Meyers) est-elle sûre?
static Singleton& instance()
{
static Singleton s;
return s;
}
Sinon, pourquoi et comment le rendre sûr?
6 réponses
Dans C++11, il est thread-safe. Selon la norme , §6.7 [stmt.dcl] p4
:
Si le contrôle entre la déclaration concurremment pendant que la variable est initialisée, l'exécution concurrente attendra {[10] } la fin de l'initialisation.
La prise en charge par GCC et VS de la fonctionnalité ( initialisation dynamique et Destruction avec concurrence, Également appelée statique magique sur MSDN) est la suivante:
- visuel Studio: pris en charge depuis Visual Studio 2015
- GCC: pris en charge depuis GCC 4.3
Merci à @ Mankarse et @ olen_gam pour leurs commentaires.
Dans C++03 , CE code n'était pas thread safe. Il y a un article de Meyers appelé "C++ et les périls du verrouillage à double vérification" qui traite des implémentations thread safe du modèle, et la conclusion est, plus ou moins, que (en C++03) verrouillage complet autour de l'instanciation la méthode est fondamentalement le moyen le plus simple d'assurer une concurrence correcte sur toutes les plates-formes, tandis que la plupart des formes de variantes de motif de verrouillage à double vérification peuvent souffrir de conditions de course sur certaines architectures, à moins que les instructions ne soient entrelacées avec des barrières de mémoire stratégiquement placées.
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, et 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 ce qui suit pseudo:
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 wrapper de classe simple pour L'objet Windows CRITICAL_SECTION afin que le compilateur initialise automatiquement le CRITICAL_SECTION
avant que main()
ne soit appelé. Idéalement, une vraie classe de section critique RAII serait utilisée pour traiter les exceptions qui pourraient se produire lorsque la section critique est tenue, mais cela dépasse le cadre de cette réponse.
L'opération fondamentale est que lorsque l'instance de Singleton
est demandée, un verrou est pris, le Singleton est créé s'il doit l'être, 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;
}
Man-c'est beaucoup de merde pour "faire un meilleur global".
Les principaux inconvénients de cette implémentation (si je n'ai pas laissé passer quelques bugs) sont:
- Si
new Singleton()
lance, le verrou ne sera pas relâché. Cela peut être corrigé en utilisant un véritable objet de verrouillage RAII au lieu du simple que j'ai ici. Cela peut aussi aidez à rendre les choses portables Si vous utilisez quelque chose comme Boost pour fournir un wrapper indépendant de la plate-forme pour le verrou. - cela garantit la sécurité du thread lorsque l'instance Singleton est demandée après l'appel de
main()
- Si vous l'appelez avant (comme dans l'initialisation d'un objet statique), les choses pourraient ne pas fonctionner carCRITICAL_SECTION
pourrait ne pas être initialisé. - un verrou doit être pris chaque fois qu'une instance est demandée. Comme je l'ai dit, il s'agit d'une implémentation simple thread safe. Si vous avez besoin d'un mieux (ou si vous voulez savoir pourquoi des choses comme la technique de verrouillage à double vérification est défectueuse), voir les documents liés à la réponse de in Groo.
En regardant la norme suivante (section 6.7.4), il explique comment l'initialisation locale statique est sûre pour les threads. Donc, une fois que cette section de la norme est largement implémentée, Singleton de Meyer sera l'implémentation préférée.
Je ne suis pas d'accord avec beaucoup de réponses déjà. 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 Faire Il threadsafe; ce n'est pas" naturallly " threadsafe.
Est la mise en œuvre suivante [...] thread safe ?
Sur la plupart des plates-formes, 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.)
Si non, 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 faire le fil coffre-fort?
"C++ and the Perils of Double-Checked Locking" par Scott Meyers et Andrei Alexandrescu est un très 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. Vérifiez la documentation. Quant à l'autre question: "sinon, 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 locaux statiques est sécurisée par les threads. Si deux threads appellent une telle fonction, un thread effectuera une initialisation tandis que l'autre bloquera et attendra sa fin.