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?

116
demandé sur Rakete1111 2009-11-02 17:09:28

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:

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.

135
répondu Groo 2017-05-23 11:54:59

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 car CRITICAL_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.
19
répondu Michael Burr 2017-05-23 11:47:28

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.

9
répondu deft_code 2009-11-02 19:38:37

La bonne réponse dépend de votre compilateur. Il peut décider de Faire Il threadsafe; ce n'est pas" naturallly " threadsafe.

5
répondu MSalters 2009-11-02 14:13:27

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.

5
répondu sbi 2009-11-02 15:00:47

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.

2
répondu sellibitze 2009-11-02 16:37:01