A implementação a seguir, usando a inicialização lenta, do Singletonthread (Meyers 'Singleton) é segura?
static Singleton& instance()
{
static Singleton s;
return s;
}
Caso contrário, por que e como torná-lo seguro?
A implementação a seguir, usando a inicialização lenta, do Singletonthread (Meyers 'Singleton) é segura?
static Singleton& instance()
{
static Singleton s;
return s;
}
Caso contrário, por que e como torná-lo seguro?
Respostas:
No C ++ 11 , é seguro para threads. De acordo com a norma , §6.7 [stmt.dcl] p4:
Se o controle digitar a declaração simultaneamente enquanto a variável estiver sendo inicializada, a execução simultânea deverá aguardar a conclusão da inicialização.
O suporte do GCC e do VS para o recurso ( Inicialização dinâmica e destruição com simultaneidade , também conhecida como estática mágica no MSDN ) é o seguinte:
Obrigado a @Mankarse e @olen_gam por seus comentários.
No C ++ 03 , esse código não era seguro para threads. Há um artigo de Meyers chamado "C ++ e os perigos do bloqueio com verificação dupla" que discute implementações seguras de threads do padrão, e a conclusão é, mais ou menos, que (no C ++ 03) o bloqueio completo em torno do método de instanciação é basicamente a maneira mais simples de garantir a simultaneidade adequada em todas as plataformas, enquanto a maioria das formas de variantes de padrão de bloqueio com verificação dupla podem sofrer condições de corrida em determinadas arquiteturas , a menos que as instruções sejam intercaladas com estrategicamente coloque barreiras de memória.
Para responder sua pergunta sobre por que não é seguro para threads, não é porque a primeira chamada para instance()deve chamar o construtor Singleton s. Para ser seguro para threads, isso teria que ocorrer em uma seção crítica, mas não há exigência no padrão de que uma seção crítica seja realizada (o padrão até o momento é completamente silencioso em threads). Os compiladores geralmente implementam isso usando uma verificação e incremento simples de um boolean 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<Singleton*>( &s)));
}
Então, aqui está um simples Singleton seguro para threads (para Windows). Ele usa um invólucro de classe simples para o objeto CRITICAL_SECTION do Windows, para que possamos fazer com que o compilador inicialize automaticamente CRITICAL_SECTIONantes de main()ser chamado. Idealmente, seria usada uma verdadeira classe de seção crítica RAII que pode lidar com exceções que podem ocorrer quando a seção crítica é realizada, mas isso está além do escopo desta resposta.
A operação fundamental é que, quando uma instância de Singletoné solicitada, um bloqueio é realizado, o Singleton é criado, se necessário, o bloqueio é liberado e a referência do Singleton retornada.
#include <windows.h>
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 - isso é muita porcaria para "fazer uma melhor global".
As principais desvantagens dessa implementação (se eu não deixei passar alguns bugs) são:
new Singleton()jogar, a trava não será liberada. Isso pode ser corrigido usando um objeto de bloqueio RAII verdadeiro, 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 invólucro independente da plataforma para o bloqueio.main()chamada - se você a chamar antes (como na inicialização de um objeto estático), as coisas podem não funcionar porque CRITICAL_SECTIONpodem não ser inicializadas.new Singleton()joga?
new Singleton()joga, definitivamente há um problema com a trava. Uma classe de bloqueio RAII adequada deve ser usada, algo como o lock_guardBoost. Queria que o exemplo fosse mais ou menos independente, e já era um monstro, então deixei de lado a segurança de exceção (mas o chamei). Talvez eu deva corrigir isso para que esse código não seja cortado e colado em algum lugar inadequado.
Observando o próximo padrão (seção 6.7.4), ele explica como a inicialização local estática é segura para threads. Portanto, uma vez que essa seção do padrão seja amplamente implementada, o Singleton da Meyer será a implementação preferida.
Eu discordo de muitas respostas já. A maioria dos compiladores já implementa a inicialização estática dessa maneira. A única exceção notável é o Microsoft Visual Studio.
O seguinte thread de implementação [...] é seguro?
Na maioria das plataformas, isso não é seguro para threads. (Anexe o aviso de isenção de responsabilidade usual, explicando que o padrão C ++ não sabe sobre threads, portanto, legalmente, não diz se é ou não.)
Se não, por que [...]?
O motivo é que nada impede que mais de um thread execute simultaneamente so construtor.
como torná-lo seguro thread?
"C ++ e os perigos do bloqueio com dupla verificação", de Scott Meyers e Andrei Alexandrescu, é um ótimo tratado sobre o assunto de singletons seguros para threads.
Como o MSalters disse: Depende da implementação do C ++ usada. 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 de threads e afirma explicitamente que a inicialização de locais estáticos é segura para threads. Se dois threads chamam essa função, um thread executará uma inicialização enquanto o outro bloqueará e aguardará o término.