c++ - patrón - singleton c# example code
¿Cómo puedo crear un patrón singleton seguro para subprocesos en Windows? (9)
He estado leyendo sobre patrones de singleton seguros para subprocesos aquí:
http://en.wikipedia.org/wiki/Singleton_pattern#C.2B.2B_.28using_pthreads.29
Y dice en la parte inferior que la única forma segura es utilizar pthread_once, que no está disponible en Windows.
¿Es esa la única forma de garantizar la inicialización segura de subprocesos?
He leído este hilo en SO:
Enlazar la construcción floja y segura de un singleton en C ++
Y parece insinuar una función de comparación y cambio de nivel de sistema operativo atómica, que supongo que en Windows es:
http://msdn.microsoft.com/en-us/library/ms683568.aspx
¿Esto puede hacer lo que quiero?
Editar: me gustaría la inicialización lenta y que solo haya una instancia de la clase.
Alguien en otro sitio mencionó usar un global dentro de un espacio de nombres (y describió un singleton como un antipatrón): ¿cómo puede ser un "antipatrón"?
Respuesta aceptada:
He aceptado la respuesta de Josh porque estoy usando Visual Studio 2008. Nota: para futuros lectores, si no está usando este compilador (o 2005), ¡no use la respuesta aceptada!
Editar: El código funciona bien, excepto la declaración de devolución. Aparece un error: error C2440: ''devolución'': no se puede convertir de ''Singleton * volátil'' a ''Singleton *''. ¿Debo modificar el valor de retorno para que sea volátil Singleton *?
Editar: Aparentemente const_cast <> eliminará el calificador volátil. Gracias de nuevo a Josh.
Aunque me gusta la solución aceptada, acabo de encontrar otra pista prometedora y pensé que debería compartirla aquí: Inicialización única (Windows)
A continuación se explica cómo hacerlo en C #, pero el mismo concepto se aplica a cualquier lenguaje de programación que admita el patrón singleton.
http://www.yoda.arachsys.com/csharp/singleton.html
Lo que necesita decidir es si desea la inicialización perezosa o no. Inicialización lenta significa que el objeto contenido dentro del singleton se crea en la primera llamada a él ex:
MySingleton::getInstance()->doWork();
si esa llamada no se realiza hasta más adelante, existe el peligro de una condición de carrera entre los hilos, como se explica en el artículo. Sin embargo, si pones
MySingleton::getInstance()->initSingleton();
al comienzo de su código, donde supone que sería seguro para subprocesos, entonces ya no es una inicialización lenta, necesitará un poco más de potencia de procesamiento cuando comience su aplicación. Sin embargo, resolverá muchos dolores de cabeza sobre las condiciones de carrera si lo hace.
Hay muchas maneras de realizar la inicialización segura de Singleton * en las ventanas. De hecho, algunos de ellos son incluso multiplataforma. En el subproceso SO al que vinculó, buscaban un Singleton que se construye de forma perezosa en C, que es un poco más específico, y puede ser un poco más complicado hacer las cosas bien, dadas las complejidades del modelo de memoria con el que está trabajando. .
- que nunca deberías usar
Hay un punto de aclaración que debe considerar para esta pregunta. ¿Necesitas ...
- Esa única instancia de una clase se crea en realidad
- Se pueden crear muchas instancias de una clase, pero solo debe haber una única instancia definitiva de la clase
Hay muchos ejemplos en la web para implementar estos patrones en C ++. Aquí hay una muestra de proyecto de código
Puede utilizar una primitiva del sistema operativo como mutex o sección crítica para asegurar la inicialización segura de subprocesos; sin embargo, esto generará una sobrecarga cada vez que acceda a su puntero único (debido a la adquisición de un bloqueo). Tampoco es portátil.
Si está usando Visual C ++ 2005/2008, puede usar el patrón de bloqueo comprobado doble, ya que " las variables volátiles se comportan como vallas ". Esta es la forma más eficiente de implementar un singleton con inicialización lenta.
Desde MSDN Magazine:
Singleton* GetSingleton()
{
volatile static Singleton* pSingleton = 0;
if (pSingleton == NULL)
{
EnterCriticalSection(&cs);
if (pSingleton == NULL)
{
try
{
pSingleton = new Singleton();
}
catch (...)
{
// Something went wrong.
}
}
LeaveCriticalSection(&cs);
}
return const_cast<Singleton*>(pSingleton);
}
Siempre que necesite acceder al singleton, simplemente llame a GetSingleton (). La primera vez que se invoca, el puntero estático se inicializará. Una vez inicializado, la verificación NULL evitará que se bloquee solo por leer el puntero.
NO use esto en cualquier compilador, ya que no es portátil. La norma no ofrece garantías sobre cómo funcionará. Visual C ++ 2005 se agrega explícitamente a la semántica de volátil para que esto sea posible.
Deberá declarar e inicializar la SECCIÓN CRÍTICA en otro lugar del código. Pero esa inicialización es barata, por lo que la inicialización lenta generalmente no es importante.
Una forma sencilla de garantizar la inicialización de un singleton segura para subprocesos multiplataforma es realizarla explícitamente (mediante una llamada a una función de miembro estático en el singleton) en el hilo principal de su aplicación antes de que su aplicación inicie cualquier otro subproceso (o al menos cualquier otro subproceso que accederá al singleton).
De esta forma, se garantiza el acceso seguro de subprocesos al singleton de la manera habitual con mutexes / secciones críticas.
La inicialización lenta también se puede lograr usando un mecanismo similar. El problema habitual encontrado con esto es que el mutex requerido para proporcionar seguridad de hilo a menudo se inicializa en el singleton mismo, lo que simplemente empuja el problema de seguridad de hilo a la inicialización de la sección mutex / crítica. Una forma de solucionar este problema es crear e inicializar una sección mutex / crítica en el hilo principal de su aplicación y luego pasarla al singleton a través de una llamada a una función miembro estática. La inicialización pesada del singleton puede ocurrir entonces de forma segura para hilos utilizando esta sección mutex / crítica preinicializada. Por ejemplo:
// A critical section guard - create on the stack to provide
// automatic locking/unlocking even in the face of uncaught exceptions
class Guard {
private:
LPCRITICAL_SECTION CriticalSection;
public:
Guard(LPCRITICAL_SECTION CS) : CriticalSection(CS) {
EnterCriticalSection(CriticalSection);
}
~Guard() {
LeaveCriticalSection(CriticalSection);
}
};
// A thread-safe singleton
class Singleton {
private:
static Singleton* Instance;
static CRITICAL_SECTION InitLock;
CRITICIAL_SECTION InstanceLock;
Singleton() {
// Time consuming initialization here ...
InitializeCriticalSection(&InstanceLock);
}
~Singleton() {
DeleteCriticalSection(&InstanceLock);
}
public:
// Not thread-safe - to be called from the main application thread
static void Create() {
InitializeCriticalSection(&InitLock);
Instance = NULL;
}
// Not thread-safe - to be called from the main application thread
static void Destroy() {
delete Instance;
DeleteCriticalSection(&InitLock);
}
// Thread-safe lazy initializer
static Singleton* GetInstance() {
Guard(&InitLock);
if (Instance == NULL) {
Instance = new Singleton;
}
return Instance;
}
// Thread-safe operation
void doThreadSafeOperation() {
Guard(&InstanceLock);
// Perform thread-safe operation
}
};
Sin embargo, existen buenas razones para evitar el uso de singletons por completo (y por qué a veces se los denomina antipatrón ):
- Son variables globales esencialmente glorificadas
- Pueden conducir a un alto acoplamiento entre partes dispares de una aplicación
- Pueden hacer que las pruebas unitarias sean más complicadas o imposibles (debido a la dificultad de intercambiar singletons reales con implementaciones falsas)
Una alternativa es hacer uso de un ''singleton lógico'' mediante el cual se crea e inicializa una sola instancia de una clase en el hilo principal y se pasa a los objetos que lo requieren. Este enfoque puede ser difícil de manejar cuando hay muchos objetos que desea crear como singletons. En este caso, los objetos dispares se pueden agrupar en un solo objeto ''Contexto'' que luego se pasa donde sea necesario.
Si está buscando una solución más portátil y fácil, puede activarla.
boost :: call_once se puede utilizar para la inicialización segura de subprocesos.
Es bastante simple de usar, y será parte del próximo estándar C ++ 0x.
La pregunta no requiere que el singleton sea de construcción lenta o no. Dado que muchas respuestas asumen eso, supongo que para la primera frase debatir:
Dado el hecho de que el lenguaje en sí no es conocimiento de subprocesos, y además de la técnica de optimización, escribir un singleton de c ++ confiable y portátil es muy difícil (si no imposible), consulte " C ++ y los peligros del bloqueo doblemente controlado " por Scott Meyers y Andrei Alexandrescu.
He visto muchas de las respuestas recurrir al objeto de sincronización en la plataforma de Windows mediante el uso de CriticalSection, pero CriticalSection solo es seguro para subprocesos cuando todos los subprocesos se ejecutan en un único procesador, hoy probablemente no sea cierto.
MSDN cite: "Los hilos de un solo proceso pueden usar un objeto de sección crítica para la sincronización de exclusión mutua".
Y http://msdn.microsoft.com/en-us/library/windows/desktop/ms682530(v=vs.85).aspx
aclararlo más:
Un objeto de sección crítica proporciona una sincronización similar a la proporcionada por un objeto mutex, excepto que una sección crítica solo puede ser utilizada por los hilos de un único proceso.
Ahora, si no se requiere un "diseño vago", la siguiente solución es segura y segura para subprocesos, e incluso portátil:
struct X { };
X * get_X_Instance()
{
static X x;
return &x;
}
extern int X_singleton_helper = (get_X_instance(), 1);
Es seguro para módulos cruzados porque usamos objetos estáticos de ámbito local en lugar de objetos globales con ámbito de espacio de archivos / nombres.
Es seguro para subprocesos porque: X_singleton_helper debe asignarse al valor correcto antes de ingresar a main o DllMain. No es una construcción floja también debido a este hecho), en esta expresión la coma es un operador, no una puntuación.
Explícitamente use "extern" aquí para evitar que el compilador lo optimice (Preocupaciones sobre el artículo de Scott Meyers, el gran enemigo es el optimizador), y también haga que la herramienta de análisis estático como pc-lint se mantenga en silencio. "Antes de main / DllMain" es Scott Meyer llamado "parte de inicio de un solo subproceso" en el elemento 4 "Effective C ++ 3rd".
Sin embargo, no estoy muy seguro de si el compilador puede optimizar la llamada get_X_instance () de acuerdo con el estándar de idioma. Comente.