Le modèle habituel pour une classe singleton est quelque chose comme
static Foo &getInst()
{
static Foo *inst = NULL;
if(inst == NULL)
inst = new Foo(...);
return *inst;
}
Cependant, je crois comprendre que cette solution n'est pas thread-safe, puisque 1) le constructeur de Foo peut être appelé plusieurs fois (ce qui peut avoir une importance) et 2) inst peut ne pas être entièrement construit avant d'être renvoyé à un autre thread .
Une solution consiste à envelopper un mutex dans toute la méthode, mais je paierai alors le temps système nécessaire à la synchronisation longtemps après en avoir réellement besoin. Une alternative est quelque chose comme
static Foo &getInst()
{
static Foo *inst = NULL;
if(inst == NULL)
{
pthread_mutex_lock(&mutex);
if(inst == NULL)
inst = new Foo(...);
pthread_mutex_unlock(&mutex);
}
return *inst;
}
Est-ce la bonne façon de procéder ou y a-t-il des pièges dont je devrais être au courant? Par exemple, existe-t-il des problèmes d'ordre d'ordre d'initialisation statiques, c'est-à-dire qu'il est toujours garanti que inst soit NULL la première fois que getInst est appelé?
Votre solution s'appelle «double vérification verrouillée» et la façon dont vous l'avez écrite n'est pas threadsafe.
Ce (papier Meyers/Alexandrescu } _ explique pourquoi - mais ce papier est également très mal compris. Il a commencé le meme «le double contrôle est dangereux en C++» - mais sa conclusion est que le double contrôle en C++ peut être mis en œuvre en toute sécurité, il nécessite simplement l’utilisation de barrières de mémoire dans un endroit non évident.
Le document contient un pseudo-code montrant comment utiliser les barrières de mémoire pour mettre en œuvre le DLCP en toute sécurité. Vous ne devriez donc pas avoir de difficulté à corriger votre mise en œuvre.
Si vous utilisez C++ 11, voici une bonne façon de procéder:
Foo& getInst()
{
static Foo inst(...);
return inst;
}
Selon la nouvelle norme, il n’est plus nécessaire de s’occuper de ce problème. L'initialisation de l'objet ne sera faite que par un seul thread, les autres threads attendront jusqu'à ce qu'il soit terminé ..__ ou vous pouvez utiliser std :: call_once. (plus d'infos ici )
Herb Sutter parle du verrouillage à double contrôle dans CppCon 2014.
Ci-dessous, le code que j'ai implémenté dans C++ 11 et basé sur cela:
class Foo {
public:
static Foo* Instance();
private:
Foo() {}
static atomic<Foo*> pinstance;
static mutex m_;
};
atomic<Foo*> Foo::pinstance { nullptr };
std::mutex Foo::m_;
Foo* Foo::Instance() {
if(pinstance == nullptr) {
lock_guard<mutex> lock(m_);
if(pinstance == nullptr) {
pinstance = new Foo();
}
}
return pinstance;
}
vous pouvez également consulter le programme complet ici: http://ideone.com/olvK13
Utilisez pthread_once
, qui garantit que la fonction d’initialisation est exécutée une fois de manière atomique.
(Sous Mac OS X, il utilise un verrou tournant. Je ne connais pas la mise en œuvre d'autres plateformes.)
TTBOMK, le seul moyen de thread-safe garanti pour le faire sans verrouillage serait d’initialiser tous vos singletons avant vous démarrez un thread.
Votre alternative s'appelle "verrouillage à double vérification" .
Il peut exister des modèles de mémoire multi-thread dans lesquels cela fonctionne, mais POSIX ne garantit pas
Est-ce que TLS fonctionne ici? https://en.wikipedia.org/wiki/Thread-local_storage#C_and_C++
Par exemple,
static _thread Foo *inst = NULL;
static Foo &getInst()
{
if(inst == NULL)
inst = new Foo(...);
return *inst;
}
Mais nous avons aussi besoin d’un moyen de le supprimer explicitement, comme
static void deleteInst() {
if (!inst) {
return;
}
delete inst;
inst = NULL;
}
L'implémentation ACE Singleton utilise un schéma de verrouillage revérifié pour la sécurité des threads; vous pouvez vous y référer si vous le souhaitez.
Vous pouvez trouver le code source ici .