A implementação da linha padrão da Singleton é segura para Meyers?

A implementação a seguir, usando boot lenta, do segmento Singleton (Meyers ‘Singleton) é segura?

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

Se não, por que e como fazer isso seguro?

    Em C ++ 11 , é thread-safe. De acordo com a norma , §6.7 [stmt.dcl] p4 :

    Se o controle entrar na declaração simultaneamente enquanto a variável estiver sendo inicializada, a execução simultânea deverá aguardar a conclusão da boot.

    O suporte do GCC e do VS para o recurso ( Inicialização Dinâmica e Destruição com Simultaneidade , também conhecido como Magic Statics no MSDN ) é o seguinte:

    • Visual Studio: suportado desde o Visual Studio 2015
    • GCC: suportado desde o GCC 4.3

    Graças a @Mankarse e @olen_gam por seus comentários.


    Em C ++ 03 , este código não foi thread-safe. Há um artigo de Meyers chamado “C ++ e os Perigos do Double-Checked Locking” que discute as implementações seguras de thread do padrão, e a conclusão é, mais ou menos, que (em C ++ 03) bloqueia completamente o método de instanciação é basicamente a maneira mais simples de garantir a simultaneidade adequada em todas as plataformas, enquanto a maioria das variantes de padrão de bloqueio com verificação dupla pode sofrer de condições de corrida em determinadas arquiteturas , a menos que as instruções sejam intercaladas com barreiras de memory estrategicamente localizadas.

    Para responder à sua pergunta sobre por que não é thread-safe, não é porque a primeira chamada para instance() deve chamar o construtor de Singleton s . Para ser threadsafe, isso teria que ocorrer em uma seção crítica, mas não há nenhum requisito no padrão que uma seção crítica seja tomada (o padrão até a data é completamente silencioso em threads). Compiladores geralmente implementam isso usando uma simples verificação e incremento de um booleano estático – mas não em uma seção crítica. Algo como o seguinte pseudocódigo:

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

    Então aqui está um singleton simples para thread (para Windows). Ele usa um invólucro de class simples para o object Windows CRITICAL_SECTION para que possamos fazer com que o compilador inicialize automaticamente o CRITICAL_SECTION antes que main() seja chamado. Idealmente, uma class de seção crítica RAII verdadeira seria usada para lidar com exceções que podem ocorrer quando a seção crítica é mantida, mas isso está além do escopo dessa resposta.

    A operação fundamental é que, quando uma instância de Singleton é solicitada, um bloqueio é feito, o Singleton é criado, se necessário, o bloqueio é liberado e a referência Singleton é retornada.

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

    Cara – é muita besteira para “fazer um mundo melhor”.

    As principais desvantagens dessa implementação (se eu não deixei alguns bugs passarem) são:

    • se o new Singleton() for lançado, o bloqueio não será liberado. Isso pode ser corrigido usando um object de bloqueio RAII real em vez do simples que tenho aqui. Isso também pode ajudar a tornar as coisas portáteis se você usar algo como o Boost para fornecer um wrapper independente de plataforma para o bloqueio.
    • isso garante a segurança do encadeamento quando a instância do Singleton é solicitada depois que main() é chamado – se você a chamar antes (como na boot de um object estático), as coisas podem não funcionar porque o CRITICAL_SECTION pode não ser inicializado.
    • um bloqueio deve ser feito sempre que uma instância for solicitada. Como eu disse, esta é uma implementação segura de thread simples. Se você precisar de um melhor (ou quiser saber por que coisas como a técnica de bloqueio de verificação dupla são falhas), veja os papéis ligados na resposta de Groo .

    Olhando para o próximo padrão (seção 6.7.4), explica como a boot local estática é segura para threads. Então, uma vez que essa seção do padrão seja amplamente implementada, o Singleton de Meyer será a implementação preferida.

    Eu discordo de muitas respostas já. A maioria dos compiladores já implementa a boot estática dessa maneira. A exceção notável é o Microsoft Visual Studio.

    A resposta correta depende do seu compilador. Pode decidir torná- lo thread-safe; não é “naturallly” threadsafe.

    O thread de implementação […] a seguir é seguro?

    Na maioria das plataformas, isso não é thread-safe. (Anexe o aviso de isenção usual, explicando que o padrão C ++ não sabe sobre threads, então, legalmente, ele não diz se é ou não.)

    Se não, por que […]?

    O motivo não é que nada impede que mais de um thread execute simultaneamente o construtor s ‘.

    como fazer isso seguro?

    “C ++ e os perigos do bloqueio duplo-verificado” por Scott Meyers e Andrei Alexandrescu é um bom tratado sobre o assunto de singletons thread-safe.

    Como MSalters disse: Depende da implementação do C ++ que você usa. Verifique a documentação. Quanto à outra pergunta: “Se não, por quê?” – O padrão C ++ ainda não menciona nada sobre threads. Mas a próxima versão do C ++ está ciente dos threads e declara explicitamente que a boot de locais estáticos é thread-safe. Se dois threads chamarem tal function, um thread executará uma boot enquanto o outro bloqueará e aguardará a conclusão.