Question

J'ai lu des informations sur les modèles de singleton thread-safe ici:

http://fr.wikipedia.org/wiki /Singleton_pattern#C.2B.2B_.28using_pthreads.29

Et il est dit en bas que le seul moyen sûr est d'utiliser pthread_once - qui n'est pas disponible sous Windows.

S'agit-il du seul moyen de garantir l'initialisation sans danger du thread?

J'ai lu ce fil sur SO:

Construction paresseuse sécurisée d'un singleton en C ++

Et semble faire allusion à une fonction d'échange et de comparaison atomique au niveau du système d'exploitation, qui, je suppose, est la suivante: Windows:

http://msdn.microsoft.com/en-us/library /ms683568.aspx

Est-ce que cela peut faire ce que je veux?

Modifier: je voudrais une initialisation lente et qu'il ne reste qu'une seule instance de la classe.

Quelqu'un sur un autre site a mentionné utiliser un caractère global dans un espace de noms (et a décrit un singleton comme un anti-modèle). Comment peut-il s'agir d'un & "anti-modèle &"??

Réponse acceptée:
J'ai accepté la réponse de Josh

Modifier: Le code fonctionne correctement, à l'exception de l'instruction return - un message d'erreur s'affiche: erreur C2440: 'return': impossible de convertir 'volatile Singleton *' en 'Singleton *'. Devrais-je modifier la valeur de retour pour qu'elle soit volatile Singleton *?

Modifier: Apparemment, const_cast < > supprime le qualificatif volatile. Merci encore à Josh.

Était-ce utile?

La solution

Si vous utilisez Visual C ++ 2005/2008, vous pouvez utiliser le schéma de verrouillage vérifié, car " les variables volatiles se comportent comme des clôtures & Quot ;. C’est le moyen le plus efficace d’implémenter un singleton initialisé paresseux.

De Magazine MSDN:

Singleton* GetSingleton()
{
    volatile static Singleton* pSingleton = 0;

    if (pSingleton == NULL)
    {
        EnterCriticalSection(&cs);

        if (pSingleton == NULL)
        {
            try
            {
                pSingleton = new Singleton();
            }
            catch (...)
            {
                // Something went wrong.
            }
        }

        LeaveCriticalSection(&cs);
    }

    return const_cast<Singleton*>(pSingleton);
}

Chaque fois que vous avez besoin d'accéder au singleton, appelez simplement GetSingleton (). Lors de son premier appel, le pointeur statique sera initialisé. Après son initialisation, la vérification de la valeur NULL empêchera le verrouillage en lisant simplement le pointeur.

NE PAS l’utiliser sur n’importe quel compilateur, car il n’est pas portable. La norme ne donne aucune garantie sur la manière dont cela fonctionnera. Visual C ++ 2005 ajoute explicitement à la sémantique de volatile pour rendre cela possible.

Vous devrez déclarer et initialiser la SECTION CRITICAL ailleurs dans le code. Mais cette initialisation n’est pas chère, une initialisation tardive n’est donc généralement pas importante.

Autres conseils

Un moyen simple de garantir une initialisation sécurisée d'un singleton par un thread multiplate-forme consiste à l'exécuter de manière explicite (via un appel à une fonction membre statique sur le singleton) dans le thread principal de votre application < strong> avant que votre application démarre avec d'autres threads (ou au moins avec d'autres threads accédant au singleton).

Il est alors possible d’obtenir l’accès sécurisé du thread au singleton de la manière habituelle avec les mutex / sections critiques.

La

initialisation différée peut également être réalisée à l'aide d'un mécanisme similaire. Le problème habituel rencontré avec cela est que le mutex requis pour assurer la sécurité des threads est souvent initialisé dans le singleton lui-même, ce qui ne fait que pousser le problème de la sécurité des threads à l'initialisation de la section mutex / critical. Une façon de résoudre ce problème consiste à créer et à initialiser une section mutex / critique dans le thread principal de votre application, puis à la transmettre au singleton via un appel à une fonction membre statique. L'initialisation lourde du singleton peut alors se produire d'une manière thread-safe en utilisant cette section mutex / critique pré-initialisée. Par exemple:

// A critical section guard - create on the stack to provide 
// automatic locking/unlocking even in the face of uncaught exceptions
class Guard {
    private:
        LPCRITICAL_SECTION CriticalSection;

    public:
        Guard(LPCRITICAL_SECTION CS) : CriticalSection(CS) {
            EnterCriticalSection(CriticalSection);
        }

        ~Guard() {
            LeaveCriticalSection(CriticalSection);
        }
};

// A thread-safe singleton
class Singleton {
    private:
        static Singleton* Instance;
        static CRITICAL_SECTION InitLock;
        CRITICIAL_SECTION InstanceLock;

        Singleton() {
            // Time consuming initialization here ...

            InitializeCriticalSection(&InstanceLock);
        }

        ~Singleton() {
            DeleteCriticalSection(&InstanceLock);
        }

    public:
        // Not thread-safe - to be called from the main application thread
        static void Create() {
            InitializeCriticalSection(&InitLock);
            Instance = NULL;
        }

        // Not thread-safe - to be called from the main application thread
        static void Destroy() {
            delete Instance;
            DeleteCriticalSection(&InitLock);
        }

        // Thread-safe lazy initializer
        static Singleton* GetInstance() {
            Guard(&InitLock);

            if (Instance == NULL) {
                Instance = new Singleton;
            }

            return Instance;
        }

        // Thread-safe operation
        void doThreadSafeOperation() {
            Guard(&InstanceLock);

            // Perform thread-safe operation
        }
};

Cependant, il existe de bonnes raisons d'éviter l'utilisation de singletons (et pourquoi ils sont parfois appelés un anti-motif ):

  • Ce sont essentiellement des variables globales glorifiées
  • Ils peuvent entraîner un couplage élevé entre différentes parties d'une application
  • Ils peuvent rendre les tests unitaires plus compliqués voire impossibles (en raison de la difficulté à échanger des singletons réels avec de fausses implémentations)

Une alternative consiste à utiliser un "singleton logique" dans lequel vous créez et initialisez une instance unique d'une classe dans le thread principal et la transmettez aux objets qui en ont besoin. Cette approche peut devenir difficile à gérer lorsqu'il existe de nombreux objets que vous souhaitez créer en tant que singletons. Dans ce cas, les objets disparates peuvent être regroupés dans un seul objet "Contexte" qui est ensuite transmis, le cas échéant.

Bien que j'aime la solution acceptée, je viens de trouver une autre piste prometteuse et j'ai pensé que je devrais la partager ici: Initialisation unique (Windows)

Vous pouvez utiliser une primitive de système d'exploitation, telle qu'un mutex ou une section critique, pour garantir l'initialisation en toute sécurité des threads. Toutefois, cela entraînera une surcharge chaque fois que vous accédez au pointeur singleton (en raison de l'acquisition d'un verrou). Il est également non portable.

Vous devez prendre en compte un point de clarification pour cette question. Avez-vous besoin de ...

  1. Cette seule et unique instance d'une classe est réellement créée
  2. Plusieurs instances d'une classe peuvent être créées, mais il ne devrait y avoir qu'une seule instance définitive de la classe

Il existe de nombreux exemples sur le Web pour implémenter ces modèles en C ++. Voici un exemple de projet de code

.

Ce qui suit explique comment procéder en C #, mais le même concept s'applique à tous les langages de programmation prenant en charge le modèle singleton

http://www.yoda.arachsys.com/csharp/singleton.html

Vous devez décider si vous souhaitez ou non une initialisation paresseuse. Initialisation différée signifie que l'objet contenu dans le singleton est créé lors du premier appel. ex:

MySingleton::getInstance()->doWork();

si cet appel n’est fait que plus tard, il existe un risque de concurrence entre les threads, comme expliqué dans l’article. Cependant, si vous mettez

MySingleton::getInstance()->initSingleton();

au tout début de votre code où vous supposez qu'il serait thread-safe, vous n'êtes plus en train d'initialiser paresseux, vous aurez besoin de & "certains &"; plus de puissance de traitement au démarrage de votre application. Cependant, cela résoudra beaucoup de maux de tête liés aux conditions de course.

Si vous recherchez une solution plus portable et plus simple, vous pouvez vous tourner vers boost.

boost :: call_once peut être utilisé pour l'initialisation thread-safe.

Il est très simple à utiliser et fera partie du prochain standard C ++ 0x.

La question ne nécessite pas que le singleton soit construit paresseux ou non. Étant donné que de nombreuses réponses supposent que, je suppose que pour la première phrase, discutez:

Compte tenu du fait que le langage lui-même n’est pas sensible aux threads et de la technique d’optimisation, écrire un singleton c ++ fiable et portable est très difficile (voire impossible), voir " C ++ et les dangers du verrouillage à double contrôle " de Scott Meyers et Andrei Alexandrescu.

J'ai vu beaucoup de réponses tenter de synchroniser un objet sur la plate-forme Windows en utilisant CriticalSection, mais CriticalSection n'est sécurisé pour les threads que lorsque tous les threads s'exécutent sur un seul processeur. Aujourd'hui, ce n'est probablement pas vrai.

MSDN cite: & "; Les threads d'un processus unique peuvent utiliser un objet de section critique pour la synchronisation par exclusion mutuelle. ".

Et http: / /msdn.microsoft.com/en-us/library/windows/desktop/ms682530(v=vs.85).aspx

clairs plus loin:

Un objet de section critique fournit une synchronisation similaire à celle fournie par un objet mutex, à l'exception du fait qu'une section critique ne peut être utilisée que par les threads d'un seul processus.

Maintenant, si & "; construit paresseux &"; n'est pas obligatoire, la solution suivante est à la fois sécurisée entre plusieurs modules et sûre pour les threads, voire portable:

struct X { };

X * get_X_Instance()
{
    static X x;
    return &x;
}
extern int X_singleton_helper = (get_X_instance(), 1);

C’est sûr pour tous les modules car nous utilisons un objet statique de portée locale au lieu d’un objet global de portée de fichier / d’espace de noms.

Il est thread-safe parce que: X_singleton_helper doit être affecté à la valeur correcte avant de saisir main ou DllMain (c’est pourquoi, la virgule n’est pas construite paresseux), dans cette expression, la virgule est un opérateur et non une ponctuation.

Utiliser explicitement " extern " ici pour empêcher le compilateur de l’optimiser (article de Scott Meyers sur les inquiétudes, le grand ennemi est l’optimiseur.), et de faire en sorte que l’outil d’analyse statique tel que pc-lint reste silencieux. " Avant main / DllMain " Scott meyer est-il appelé & "; partie de démarrage mono-threadée &"; dans " Effective C ++ 3rd " point 4.

Cependant, je ne sais pas si le compilateur est autorisé à optimiser l'appel appelé get_X_instance () en fonction de la norme de langue. Veuillez commenter.

Il existe de nombreuses façons d’initialiser l’initialisation Singleton * avec thread-safe sur Windows. En fait, certaines d'entre elles sont même multiplateformes. Dans le fil SO que vous avez lié, ils recherchaient un Singleton paresseusement construit en C, ce qui est un peu plus spécifique et peut être un peu plus difficile à faire correctement, étant donné les subtilités du modèle de mémoire sous lequel vous travaillez. .

  • que vous ne devriez jamais utiliser
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top