A implementação a seguir, usando a inicialização lenta, do Singleton
thread (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 Singleton
thread (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_SECTION
antes 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_SECTION
podem 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_guard
Boost. 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 s
o 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.