Singleton efficiente thread-safe in C ++

Il solito schema per una class singleton è qualcosa di simile

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

Tuttavia, è a mia conoscenza 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 dovrebbe importare) e 2) inst potrebbe non essere completamente costruito prima di essere restituito a un thread differente .

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 ​​di cui dovrei essere a conoscenza? Ad esempio, ci sono dei problemi di ordine di inizializzazione statici che potrebbero verificarsi, ovvero che inst è sempre garantito come NULL la prima volta che viene chiamato getInst?

La tua soluzione si chiama ‘double checked locking’ e il modo in cui lo hai scritto non è protetto da thread.

Questo documento di Meyers / Alexandrescu spiega perché – ma anche quella carta è 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 luogo non ovvio.

Il documento contiene uno pseudocodice che dimostra come utilizzare le barriere di memoria per implementare in modo sicuro il DLCP, quindi non dovrebbe essere difficile per te correggere la tua implementazione.

Se stai usando C ++ 11, ecco un modo giusto per farlo:

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

Secondo il nuovo standard non c’è più bisogno di preoccuparsi di questo problema. L’inizializzazione dell’object verrà eseguita solo da un thread, gli altri thread attenderanno fino al completamento. Oppure puoi usare std :: call_once. (maggiori informazioni qui )

Herb Sutter parla del blocco a doppio controllo in CppCon 2014.

Di seguito è riportato il codice che ho implementato in C ++ 11 basato su quello:

 class Foo { public: static Foo* Instance(); private: Foo() {} static atomic pinstance; static mutex m_; }; atomic Foo::pinstance { nullptr }; std::mutex Foo::m_; Foo* Foo::Instance() { if(pinstance == nullptr) { lock_guard lock(m_); if(pinstance == nullptr) { pinstance = new Foo(); } } return pinstance; } 

puoi anche controllare il programma completo qui: http://ideone.com/olvK13

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

(Su Mac OS X utilizza un blocco di selezione. Non so l’implementazione di altre piattaforms.)

TTBOMK, l’unico modo sicuro per fare il thread senza bloccare sarebbe quello di inizializzare tutti i tuoi singleton prima di iniziare una discussione.

La tua alternativa è chiamata “blocco a doppio controllo” .

Potrebbero esistere modelli di memoria multi-thread in cui funziona, ma POSIX non ne garantisce uno

L’implementazione di singleton ACE utilizza un modello di blocco a doppia verifica per la sicurezza dei thread, è ansible fare riferimento ad esso se lo si desidera.

Puoi trovare il codice sorgente qui .

TLS funziona qui? https://en.wikipedia.org/wiki/Thread-local_storage#C_and_C++

Per esempio,

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

Ma abbiamo anche bisogno di un modo per eliminarlo esplicitamente, come

 static void deleteInst() { if (!inst) { return; } delete inst; inst = NULL; } 

La soluzione non è thread-safe perché la dichiarazione

 inst = new Foo(); 

può essere scomposto in due dichiarazioni dal compilatore:

 inst = malloc(sizeof(Foo)); inst->Foo(); 

Quindi se dopo l’esecuzione dell’istruzione 1 di un thread un altro thread esegue il metodo getInstance() , allora troverà che il puntatore non è nullo e quindi restituirà il puntatore all’object non inizializzato.