L’implementazione di Meyers per il thread del pattern Singleton è sicura?

La seguente implementazione, utilizzando l’inizializzazione pigra, del thread Singleton (Meyers ‘Singleton) è sicura?

 static Singleton& instance() { static Singleton s; return s; } 

In caso negativo, perché e come renderlo thread-safe?

In C ++ 11 , è thread-safe. Secondo lo standard , §6.7 [stmt.dcl] p4 :

Se il controllo immette la dichiarazione contemporaneamente durante l’inizializzazione della variabile, l’ esecuzione simultanea deve attendere il completamento dell’inizializzazione.

Il supporto GCC e VS per la funzione ( Inizializzazione e distruzione dynamic con concorrenza , noto anche come Magic Statics su MSDN ) è il seguente:

  • Visual Studio: supportato da Visual Studio 2015
  • GCC: supportato dal GCC 4.3

Grazie a @Mankarse e @olen_gam per i loro commenti.


In C ++ 03 , questo codice non era thread-safe. C’è un articolo di Meyers intitolato “C ++ e i pericoli del blocco a doppio controllo” che discute le implementazioni thread safe del pattern, e la conclusione è, più o meno, che (in C ++ 03) pieno blocco attorno al metodo di istanziazione è fondamentalmente il modo più semplice per garantire una concorrenza adeguata su tutte le piattaforms, mentre la maggior parte delle varianti di modelli di blocco a doppio controllo può soffrire di condizioni di competizione su determinate architetture , a meno che le istruzioni non siano intercalate con barriere di memoria strategicamente posizionate.

Per rispondere alla tua domanda sul motivo per cui non è protetto da thread, non è perché la prima chiamata a instance() deve chiamare il costruttore per Singleton s . Per essere thread-safe, ciò dovrebbe verificarsi in una sezione critica, ma nello standard non è richiesto che venga presa una sezione critica (lo standard è completamente silenzioso sui thread). I compilatori spesso lo implementano utilizzando un semplice controllo e l’incremento di un valore booleano statico, ma non in una sezione critica. Qualcosa come il seguente pseudocodice:

 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( &s))); } 

Quindi ecco un semplice Singleton sicuro per thread (per Windows). Utilizza un semplice wrapper di class per l’object CRITICAL_SECTION di Windows in modo che il compilatore possa inizializzare automaticamente CRITICAL_SECTION prima che venga chiamato main() . Idealmente, verrebbe utilizzata una vera sezione di sezione critica RAII in grado di gestire le eccezioni che potrebbero verificarsi quando si tiene la sezione critica, ma ciò va oltre lo scopo di questa risposta.

L’operazione fondamentale è che quando viene richiesta un’istanza di Singleton , viene eseguito un blocco, Singleton viene creato se è necessario, quindi il blocco viene rilasciato e viene restituito il riferimento Singleton.

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

Man: è un sacco di merda per “rendere un mondo migliore”.

I principali svantaggi di questa implementazione (se non ho lasciato passare alcuni bug) sono:

  • se un new Singleton() lancia, il blocco non verrà rilasciato. Questo può essere risolto usando un vero object di blocco RAII invece di quello semplice che ho qui. Ciò può anche rendere le cose portabili se si utilizza qualcosa come Boost per fornire un wrapper indipendente dalla piattaforma per il lock.
  • questo garantisce la sicurezza del thread quando viene richiesta l’istanza Singleton dopo che è stato chiamato main() – se lo si chiama prima (come nell’inizializzazione di un object statico) le cose potrebbero non funzionare perché CRITICAL_SECTION potrebbe non essere inizializzato.
  • una serratura deve essere presa ogni volta che viene richiesta un’istanza. Come ho detto, questa è una semplice implementazione thread-safe. Se ne hai bisogno di uno migliore (o vuoi sapere perché cose come la tecnica del blocco a doppio controllo è difettoso), vedi i documenti collegati nella risposta di Groo .

Osservando lo standard successivo (sezione 6.7.4), si evince come l’inizializzazione locale statica sia infallibile. Quindi, una volta implementata la sezione standard, Meyer’s Singleton sarà l’implementazione preferita.

Non sono d’accordo con molte risposte già. La maggior parte dei compilatori implementano già l’inizializzazione statica in questo modo. L’unica eccezione degna di nota è Microsoft Visual Studio.

La risposta corretta dipende dal tuo compilatore. Può decidere di renderlo sicuro; non è “naturalmente” sicuro.

La seguente […] implementazione thread è sicura?

Sulla maggior parte delle piattaforms, questo non è thread-safe. (Aggiungere il consueto diniego di responsabilità che spiega che lo standard C ++ non conosce i thread, quindi, legalmente, non dice se lo sia o meno.)

Se no, perché […]?

La ragione per cui non lo è è che nulla impedisce più di un thread di eseguire simultaneamente s costruttore di s .

come renderlo thread sicuro?

“C ++ and the Perils of Double-Checked Locking” di Scott Meyers e Andrei Alexandrescu è un bel trattato sull’argomento dei singleton “thread-safe”.

Come ha detto MSalters: Dipende dall’implementazione del C ++ che usi. Controlla la documentazione. Per quanto riguarda l’altra domanda: “Se no, perché?” – Lo standard C ++ non menziona ancora nulla sui thread. Ma la prossima versione di C ++ è a conoscenza dei thread e afferma esplicitamente che l’inizializzazione dei locals statici è thread-safe. Se due thread chiamano tale funzione, un thread eseguirà un’inizializzazione mentre l’altro bloccherà e attenderà che finisca.