2009-11-02 9 views
105

¿Es segura la implementación siguiente, mediante la inicialización diferida, de Singleton (Meyers 'Singleton)?¿Es segura la implementación de Meyers del hilo del patrón Singleton?

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

Si no, ¿por qué y cómo hacerlo seguro?

+0

¿Puede alguien explicar por qué esto no es seguro para subprocesos? Los artículos mencionados en los enlaces analizan la seguridad de subprocesos utilizando una implementación alternativa (utilizando una variable de puntero, es decir, Singleton * pInstance estática). – Ankur

+1

Ver: http: // stackoverflow.com/questions/449436/singleton-instance-declared-as-static-variable-of-getinstance-method/449823 # 449823 –

+0

Ver: http://stackoverflow.com/questions/1008019/c-singleton-design-pattern/ 1008289 # 1008289 –

Respuesta

124

En C++11, es seguro para subprocesos. De acuerdo con la standard, §6.7 [stmt.dcl] p4:

Si el control entra la declaración al mismo tiempo, mientras que la variable se inicializa, el ejecución concurrente deberá esperar para la terminación de la inicialización.

GCC y el apoyo VS para la característica (Dynamic Initialization and Destruction with Concurrency, también conocido como Magic Statics on MSDN) es el siguiente:

Gracias a @Mankarse y @olen_gam por sus comentarios.


En C++03, este código no se hilo de seguridad. Hay un artículo de Meyers llamado "C++ and the Perils of Double-Checked Locking" que trata las implementaciones seguras de subprocesos del patrón, y la conclusión es, más o menos, que (en C++ 03) el bloqueo completo alrededor del método de creación de instancias es básicamente la forma más sencilla de garantizar una concurrencia adecuada en todas las plataformas, mientras que la mayoría de las formas de variantes de patrón de bloqueo con doble verificación pueden sufrir de race conditions on certain architectures, a menos que las instrucciones se intercalen estratégicamente coloca barreras de memoria.

+4

+1 para enlaces a buenos artículos. – Ankur

+2

También hay una extensa discusión sobre el patrón Singleton (duración e hilo) por Alexandrescu en Modern C++ Design. Visite el sitio de Loki: http://loki-lib.sourceforge.net/index.php?n=Pattern.Singleton –

+0

Puede crear un singleton seguro para subprocesos con boost :: call_once. – CashCow

6

La respuesta correcta depende de su compilador. Puede decidir make it threadsafe; no es "naturallly" threadsafe.

18

Para responder a su pregunta acerca de por qué no es seguro para la rosca, no es porque la primera llamada a instance() debe llamar al constructor para Singleton s. Para que sea seguro, esto debería ocurrir en una sección crítica, pero no hay ningún requisito en el estándar de que se tome una sección crítica (el estándar hasta la fecha es completamente silencioso en los hilos). Los compiladores a menudo implementan esto usando una comprobación simple y un incremento de un booleano estático, pero no en una sección crítica. Algo así como el pseudocódigo siguiente:

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

Así que aquí hay un Singleton sencillo seguro para subprocesos (para Windows). Utiliza un contenedor de clase simple para el objeto CRITICAL_SECTION de Windows para que podamos hacer que el compilador inicialice automáticamente el CRITICAL_SECTION antes de llamar al main(). Idealmente, se utilizaría una verdadera clase de sección crítica de RAII que puede tratar con las excepciones que pueden ocurrir cuando se lleva a cabo la sección crítica, pero eso está más allá del alcance de esta respuesta.

El funcionamiento fundamental es que cuando se solicita una instancia de Singleton, se realiza un bloqueo, se crea Singleton si es necesario, se libera el bloqueo y se devuelve la referencia de Singleton.

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

Hombre - eso es un montón de basura para "hacer un mundo mejor".

Los principales inconvenientes de este IMPLEMENTACIÓN (si no dejar que algunos insectos se deslizan a través) es:

  • si new Singleton() tiros, la cerradura no se dará a conocer. Esto se puede solucionar utilizando un verdadero objeto de bloqueo RAII en lugar del simple objeto que tengo aquí. Esto también puede ayudar a que las cosas sean portátiles si usa algo como Boost para proporcionar un contenedor independiente de la plataforma para el bloqueo.
  • esto garantiza seguridad de subprocesos cuando se solicita la instancia de Singleton después de llamar al main(); si lo llama antes (como en la inicialización de un objeto estático) es posible que las cosas no funcionen porque CRITICAL_SECTION podría no inicializarse.
  • Se debe realizar un bloqueo cada vez que se solicite una instancia. Como dije, esta es una implementación sencilla y segura. Si necesita uno mejor (o si desea saber por qué elementos como la técnica de bloqueo de doble verificación son defectuosos), consulte el papers linked to in Groo's answer.
+0

Buena explicación. Gracias. – Ankur

+8

+1 'eso es un montón de basura para "hacer un mundo mejor"' - ¡eso hizo reír! ;-) –

+1

Uh oh. ¿Qué sucede si 'nuevo Singleton()' arroja? – sbi

5

¿Es seguro [...] el siguiente subproceso de implementación?

En la mayoría de las plataformas, esto no es seguro para subprocesos. (Anexar la renuncia de costumbre explicando que el estándar de C++ no sabe nada de las discusiones, por lo que, legalmente, no dice si se trata o no.)

Si no es así, ¿por qué [...]?

La razón por la que no lo es es que nada impide que más de un hilo ejecute simultáneamente el constructor s '.

cómo hacerlo seguro?

"C++ and the Perils of Double-Checked Locking" de Scott Meyers y Andrei Alexandrescu es un buen tratado sobre el tema de singletons seguros para subprocesos.

2

Como dijo MSalters: Depende de la implementación de C++ que utilice. Verifique la documentación. En cuanto a la otra pregunta: "Si no, ¿por qué?" - El estándar de C++ aún no menciona nada sobre los hilos. Pero la próxima versión de C++ es consciente de los hilos y establece explícitamente que la inicialización de los locales estáticos es segura para subprocesos. Si dos hilos llaman a esa función, un hilo realizará una inicialización mientras que el otro bloqueará & espere a que termine.

9

En cuanto al siguiente estándar (sección 6.7.4), explica cómo la inicialización local estática es segura para subprocesos. Entonces, una vez que esa sección de estándar se implemente ampliamente, Meyers Singleton será la implementación preferida.

No estoy de acuerdo con muchas respuestas. La mayoría de los compiladores ya implementan la inicialización estática de esta manera. La única excepción notable es Microsoft Visual Studio.

Cuestiones relacionadas