Domanda

Ho letto su thread-safe singleton pattern qui:

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

E si dice in fondo che l'unico modo sicuro è quello di utilizzare pthread_once - che non è disponibile in Windows.

È che il solo modo di garantire thread-safe di inizializzazione?

Ho letto questo thread COSÌ:

Thread-safe pigro costruzione di un singleton in C++

E sembra presagire un'atomica a livello di sistema operativo swap e la funzione di confronto, che presumo su Windows è:

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

Questa può fare ciò che voglio?

Edit: Vorrei pigro inizializzazione e per non essere solo una istanza della classe.

Qualcuno su un altro sito menzionato con un globale all'interno di uno spazio dei nomi, e ha descritto un singleton come un anti-pattern) - come può essere un "anti-pattern"?

Accettato Risposta:
Ho accettato Josh risposta come sto utilizzando Visual Studio 2008 - NB:Per i futuri lettori, se non si utilizza questo compilatore (o 2005) - non utilizzare accettato di rispondere!!!!

Edit: Il codice funziona bene, tranne il ritorno di istruzione ottengo un errore:l'errore c2440 errore:'ritorno' :impossibile convertire da 'volatile Singleton *' a 'Singleton *'.Devo modificare il valore di ritorno ad essere volatili Singleton *?

Edit: A quanto pare const_cast<> rimuovere il qualificatore volatile.Grazie ancora a Josh.

È stato utile?

Soluzione

Se si utilizza Visual C ++ 2005/2008 è possibile utilizzare il modello di blocco con doppio controllo, poiché " le variabili volatili si comportano come recinzioni & Quot ;. Questo è il modo più efficiente per implementare un singleton inizializzato pigro.

Da MSDN Magazine:

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

Ogni volta che è necessario accedere al singleton, basta chiamare GetSingleton (). La prima volta che viene chiamato, il puntatore statico verrà inizializzato. Dopo l'inizializzazione, il controllo NULL impedirà il blocco solo per la lettura del puntatore.

NON utilizzare questo su qualsiasi compilatore, poiché non è portatile. Lo standard non fornisce garanzie su come funzionerà. Visual C ++ 2005 aggiunge esplicitamente alla semantica di volatile per renderlo possibile.

Dovrai dichiarare e inizializzare la SEZIONE CRITICA altrove nel codice. Ma questa inizializzazione è economica, quindi l'inizializzazione lenta di solito non è importante.

Altri suggerimenti

Un modo semplice per garantire l'inizializzazione sicura del thread multipiattaforma di un singleton è eseguirlo esplicitamente (tramite una chiamata a una funzione membro statica sul singleton) nel thread principale dell'applicazione < strong> prima l'applicazione avvia altri thread (o almeno altri thread che accederanno al singleton).

Garantire quindi l'accesso sicuro al thread al singleton nel modo consueto con mutex / sezioni critiche.

Inizializzazione lenta può anche essere ottenuta utilizzando un meccanismo simile. Il solito problema riscontrato in questo è che il mutex richiesto per fornire la sicurezza del thread viene spesso inizializzato nel singleton stesso, il che spinge semplicemente il problema di sicurezza del thread all'inizializzazione della sezione mutex / critica. Un modo per ovviare a questo problema è creare e inizializzare una sezione mutex / critica nel thread principale dell'applicazione, quindi passarla al singleton tramite una chiamata a una funzione membro statica. L'inizializzazione dei pesi massimi del singleton può quindi avvenire in modo thread-safe utilizzando questa sezione mutex / critica pre-inizializzata. Ad esempio:

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

Tuttavia, ci sono buoni motivi per evitare del tutto l'uso dei singoli (e perché a volte vengono chiamati anti-pattern ):

  • Sono essenzialmente variabili globali glorificate
  • Possono portare a un elevato accoppiamento tra parti disparate di un'applicazione
  • Possono rendere i test unitari più complicati o impossibili (a causa del difficile scambio di singoli reali con implementazioni false)

Un'alternativa è quella di utilizzare un "singleton logico" in base al quale creare e inizializzare una singola istanza di una classe nel thread principale e passarla agli oggetti che la richiedono. Questo approccio può diventare ingombrante dove ci sono molti oggetti che si desidera creare come singoli. In questo caso, gli oggetti disparati possono essere raggruppati in un singolo oggetto "Contesto" che viene quindi passato dove necessario.

Mentre mi piace la soluzione accettata, ho appena trovato un altro promettente vantaggio e ho pensato di condividerla qui: Inizializzazione singola (Windows)

È possibile utilizzare una primitiva del sistema operativo come mutex o sezione critica per garantire l'inizializzazione sicura dei thread, tuttavia ciò comporterà un sovraccarico ogni volta che si accede al puntatore singleton (a causa dell'acquisizione di un blocco). È anche non portatile.

C'è un punto di chiarimento che devi considerare per questa domanda. Hai bisogno di ...

  1. Quell'unica e unica istanza di una classe viene mai effettivamente creata
  2. È possibile creare molte istanze di una classe ma dovrebbe esserci solo una vera istanza definitiva della classe

Esistono molti esempi sul Web per implementare questi schemi in C ++. Ecco un Esempio di progetto di codice

Di seguito viene spiegato come farlo in C#, ma l'esatto stesso concetto vale per qualsiasi linguaggio di programmazione che supportano il singleton pattern

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

Quello che devi decidere è se si desidera lazy initialization o non.Lazy initialization significa che l'oggetto contenuto all'interno del singleton viene creata la prima chiamata ex :

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

se la chiamata isnt fino al più tardi, c'è il pericolo di una condizione di competizione tra i fili, come spiegato nell'articolo.Tuttavia, se si mette

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

all'inizio del codice in cui si assume sarebbe thread-safe, quindi non è più pigro di inizializzazione, si richiede che "alcuni" più potenza di elaborazione quando l'applicazione viene avviata.Tuttavia non si risolve un sacco di mal di testa sulle condizioni di competizione se si fa così.

Se stai cercando una soluzione più portatile e più semplice, potresti rivolgerti per aumentare.

boost :: call_once può essere utilizzato per l'inizializzazione thread-safe.

È piuttosto semplice da usare e farà parte del prossimo standard C ++ 0x.

La domanda non richiede che il singleton sia o no pigro. Poiché molte risposte lo ipotizzano, suppongo che per la prima frase si discuti:

Dato che il linguaggio stesso non è sensibile al thread e, oltre alla tecnica di ottimizzazione, scrivere un singleton c ++ portatile affidabile è molto difficile (se non impossibile), vedere " C ++ e i pericoli del doppio controllo controllato " di Scott Meyers e Andrei Alexandrescu.

Ho visto molti dei resort di risposta per sincronizzare oggetti sulla piattaforma Windows usando CriticalSection, ma CriticalSection è sicuro per i thread solo quando tutti i thread sono in esecuzione su un singolo processore, oggi probabilmente non è vero.

MSDN cite: " I thread di un singolo processo possono utilizzare un oggetto di sezione critica per la sincronizzazione di mutua esclusione. quot &;.

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

chiariscilo ulteriormente:

Un oggetto di sezione critica fornisce una sincronizzazione simile a quella fornita da un oggetto mutex, tranne per il fatto che una sezione critica può essere utilizzata solo dai thread di un singolo processo.

Ora, se " lazy-built " non è un requisito, la seguente soluzione è sia cross-module sicura che thread-safe e persino portatile:

struct X { };

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

È sicuro tra i moduli perché utilizziamo un oggetto statico con ambito locale invece dell'oggetto globale con ambito file / namespace.

È thread-safe perché: X_singleton_helper deve essere assegnato al valore corretto prima di inserire main o DllMain Non è costruito in modo pigro anche per questo motivo), in questa espressione la virgola è un operatore, non punteggiatura.

Usa esplicitamente " extern " qui per impedire al compilatore di ottimizzarlo (Preoccupazioni sull'articolo di Scott Meyers, il grande nemico è l'ottimizzatore.) e anche far tacere lo strumento di analisi statica come PC-Lint. " Prima di main / DllMain " è Scott meyer chiamato " parte di avvio a thread singolo " tra " Efficace C ++ 3rd " punto 4.

Tuttavia, non sono molto sicuro che al compilatore sia consentito ottimizzare la chiamata get_X_instance () in base allo standard linguistico, si prega di commentare.

Esistono molti modi per eseguire l'inizializzazione di Singleton * thread-safe su Windows. In effetti alcuni di essi sono addirittura multipiattaforma. Nel thread SO a cui ti sei collegato, stavano cercando un Singleton che è pigramente costruito in C, che è un po 'più specifico e può essere un po' più complicato da fare nel modo giusto, date le complessità del modello di memoria su cui stai lavorando .

  • che non dovresti mai usare
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top