2010-04-04 9 views
56

Il modello usuale per una classe Singleton è qualcosa di simileefficiente Singleton thread-safe in C++

static Foo &getInst() 
{ 
    static Foo *inst = NULL; 
    if(inst == NULL) 
    inst = new Foo(...); 
    return *inst;  
} 

Tuttavia, è la mia comprensione che questa soluzione non è thread-safe, dal momento che 1) il costruttore di Foo potrebbe essere chiamato più di una volta (che può o non può importare) e 2) inst può non essere completamente costruito prima che venga restituito a un thread diverso.

Una soluzione è racchiudere un mutex attorno all'intero metodo, ma poi sto pagando il sovraccarico della sincronizzazione per molto tempo dopo che ne ho effettivamente bisogno. Un'alternativa è qualcosa di simile

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

È questo il modo giusto per farlo, o ci sono delle insidie ​​che dovrei essere a conoscenza? Ad esempio, ci sono problemi di ordine di inizializzazione statici che potrebbero verificarsi, vale a dire inst è sempre garantito che sia NULL la prima volta che viene chiamato getInst?

+5

Ma voi non hai tempo per trovare un esempio e iniziare un voto ravvicinato? Sono fresco fuori al momento. – bmargulies

+1

possibile duplicato di http://stackoverflow.com/questions/6915/thread-safe-lazy-contruction-of-a-singleton-in-c – kennytm

+3

@bmargulies No, l'interrogante ovviamente non poteva essere infastidito, quindi perché IO? Ho deciso di rinunciare al downvoting e alla chiusura come facinorosi, dato che mi sembra di essere uno dei pochi a preoccuparsi di continuare a fare i conti con SO. E sai, la pigrizia ti fa sentire bene! –

risposta

35

La soluzione si chiama 'blocco ricontrollato' e il modo che hai scritto non è threadsafe.

Questo Meyers/Alexandrescu paper spiega perché - ma quella carta è anche ampiamente fraintesa. Ha avviato il blocco "double checked locking is safe in C++", ma la sua conclusione effettiva è che il doppio blocco controllato in C++ può essere implementato in modo sicuro, richiede solo l'uso di barriere di memoria in un posto non ovvio.

La carta contiene pseudocodice che dimostra come utilizzare le barriere di memoria per implementare in modo sicuro il DLCP, quindi non dovrebbe essere difficile per voi correggere la vostra implementazione.

+0

if (inst == NULL) {temp = new Foo (...); inst = temp;} Non garantisce che il costruttore abbia finito prima che l'inst sia stato assegnato? Mi rendo conto che può (e probabilmente sarà) ottimizzato via, ma logicamente questo risolve il problema, no? – stu

+0

Che doesn Aiutatemi perché un compilatore conforme è libero di riordinare le fasi di assegnazione e costruzione come meglio crede. –

+0

Ho letto attentamente il documento, e sembra che la raccomandazione sia semplicemente evitare DLCP con Singleton. essere volatili al di fuori della classe e aggiungere barriere alla memoria (non influirebbe anche sull'efficienza?). Per esigenze pratiche, utilizzare un semplice blocco singolo e memorizzare nella cache l'oggetto ottenuto da "GetInstance". – guyarad

8

Utilizzare pthread_once, che garantisce che la funzione di inizializzazione venga eseguita una volta atomicamente.

(In Mac OS X si utilizza un blocco di selezione. Non so la realizzazione di altre piattaforme.)

2

TTBOMK, l'unico modo thread-safe garantito per fare questo senza bloccare sarebbe per inizializzare tutte le vostre singleton prima di si avvia una discussione.

0

L'alternativa si chiama "double-checked locking".

Ci potrebbe esistere modelli multi-threaded di memoria in cui opera, ma POSIX non garantisce una

0

L'implementazione di singleton ACE utilizza un modello di blocco a doppia verifica per la sicurezza della filettatura, è possibile fare riferimento se lo si desidera.

È possibile trovare il codice sorgente here.

61

Se si utilizza C++ 11, ecco un modo giusto per fare questo:

Foo& getInst() 
{ 
    static Foo inst(...); 
    return inst; 
} 

Secondo la nuova norma non v'è alcuna necessità di preoccuparsi di questo problema più. L'inizializzazione dell'oggetto verrà eseguita solo da un thread, gli altri thread attenderanno fino al completamento. Oppure puoi usare std :: call_once.(+ Info here)

+1

Questa è la soluzione C++ 11 Mi aspetterei che le persone implementassero. – Alex

+8

Purtroppo, questo non è thread-safe in VS2013, vedere "Statistica magica" qui: http://msdn.microsoft.com/en-gb/library/hh567368.aspx –

+3

VS 14 sembra risolvere questo problema - http: // blogs.msdn.com/b/vcblog/archive/2014/06/03/visual-studio-14-ctp.aspx –

7

Herb Sutter talks about the double-checked locking in CppCon 2014.

Di seguito è il codice ho implementato in C++ 11 sulla base di tale:

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

è anche possibile controllare programma completo qui: http://ideone.com/olvK13

+1

Perché implementare il doppio controllo quando hai già C++ 11? – Etherealone

+1

@Etherealone qual è il tuo suggerimento? – qqibrow

+1

Un semplice 'statico Foo pippo;' e 'restituisce &foo;' all'interno della funzione di istanza sarebbe sufficiente; L'inizializzazione 'static' è thread-safe in C++ 11. Preferisco il riferimento ai puntatori però. – Etherealone