124 votes

Mise en œuvre de Meyers de fil pattern Singleton est sécuritaire ?

Est l’implémentation suivante, à l’aide de l’initialisation tardive, de thread-safe Singleton (Meyers Singleton) ?

Si non, pourquoi et comment le rendre thread safe ?

146voto

Groo Points 19453

En C++11, il est thread-safe. Selon la norme, §6.7 [stmt.dcl] p4:

Si le contrôle d'entrée la déclaration simultanément tandis que la variable est initialisée, l' exécution en simultané doit attendre pour l'achèvement de la phase d'initialisation.

Grâce à @Mankarse pour le signaler.


En C++03, ce code n'est pas thread-safe.

Il y a un article par Meyers, qui traite de thread-safe implémentations du modèle (ainsi que beaucoup d'autres sur le net):

19voto

Michael Burr Points 181287

Pour répondre à votre question de savoir pourquoi il n'est pas thread-safe, ce n'est pas parce que le premier appel d' instance() doit appeler le constructeur de Singleton s. Pour être thread-safe cela devrait se produire dans une section critique, mais il n'est pas obligatoire dans la norme qu'une section critique (de la standard à ce jour est totalement silencieux sur les threads). Les compilateurs souvent de mettre en œuvre ce à l'aide d'une simple vérification et de l'incrément de static boolean - mais pas dans une section critique. Quelque chose comme le pseudo-code 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 thread-safe Singleton (pour Windows). Il utilise un simple wrapper de classe pour Windows CRITICAL_SECTION objet, de telle sorte que nous puissions avoir le compilateur initialiser automatiquement l' CRITICAL_SECTION avant main() est appelé. Idéalement, un vrai RAII section critique de la classe serait utilisé pour traiter les exceptions qui peuvent se produire lorsque la section critique est de la tenue, mais c'est au-delà de la portée de cette réponse.

L'opération fondamentale est que lorsqu'une instance d' Singleton est demandé, un verrou, le Singleton est créé s'il doit l'être, alors le verrou est libéré et le Singleton de référence retourné.

#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;
}

L'homme qui a beaucoup de merde de "faire mieux".

Les principaux inconvénients de cette mise en œuvre (si je n'ai pas laissé de quelques bugs passer à travers) est:

  • si new Singleton() lancers, la serrure ne sera pas publié. Ceci peut être résolu en utilisant une vraie RAII verrouillage de l'objet au lieu de la simple que j'ai ici. Cela peut aussi aider à rendre les choses portable si vous utilisez quelque chose comme coup de pouce pour fournir une plate-forme indépendante de l'enveloppe pour le verrouiller.
  • cela garantit la sécurité des threads lorsque l'instance du Singleton est demandé après l' main() est appelé - si vous appelez avant (comme dans un objet statique de l'initialisation) des choses peut ne pas fonctionner en raison de l' CRITICAL_SECTION pourrait ne pas être initialisé.
  • une serrure doit être prise chaque fois qu'une instance est demandé. Comme je l'ai dit, c'est un simple thread-safe mise en œuvre. Si vous avez besoin d'un meilleur (ou qui veulent savoir le pourquoi des choses comme la double-vérifier la technique de verrouillage est erronée), voir les documents liés à Groo de réponse.

9voto

deft_code Points 19418

En regardant la prochaine norme (section 6.7.4), il explians comment l’initialisation locale statique est thread-safe. Donc une fois que l’article de la norme largement implémentée, Singleton de Meyer sera la mise en œuvre préférée.

Je suis en désaccord 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.

6voto

MSalters Points 74024

La bonne réponse dépend de votre compilateur. Il peut décider de rendre ce thread-safe ; Il n’est pas thread-safe « naturallly ».

5voto

sbi Points 100828

Est la suite de la mise en œuvre [ ... ] "thread-safe"?

Sur la plupart des plates-formes, ce n'est pas thread-safe. (Ajout de l'avertissement habituel expliquant que la norme C++ ne sait pas à propos des threads, donc, légalement, il ne dit pas si elle est ou non).

Si non, pourquoi [...]?

La raison pour laquelle il n'est pas, c'est que rien n'empêche plus d'un fil à partir de tout en exécutant s' constructeur.

comment faire thread-safe?

"C++ et les Dangers de la Double vérification de Verrouillage" par Scott Meyers et Andrei Alexandrescu est un très bon traité sur le sujet de thread-safe singletons.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X