Pregunta

He estado leyendo sobre patrones 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 usar pthread_once, que no está disponible en Windows.

¿Es ese el solo ¿Manera de garantizar la inicialización segura para subprocesos?

He leído este hilo en SO:

Construcción perezosa segura para subprocesos de un singleton en C++

Y parece insinuar una función atómica de intercambio y comparación a nivel del sistema operativo, que supongo que en Windows es:

http://msdn.microsoft.com/en-us/library/ms683568.aspx

¿Puede esto hacer lo que quiero?

Editar: Me gustaría una inicialización diferida y que solo haya una instancia de la clase.

Alguien en otro sitio mencionó el uso de 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 ya que estoy usando Visual Studio 2008 - NB:Para futuros lectores, si no están utilizando este compilador (o 2005), ¡no utilicen la respuesta aceptada!

Editar: El código funciona bien excepto la declaración de devolución; aparece un error:error C2440:'devolver' :no se puede convertir de 'Singleton volátil *' a 'Singleton *'.¿Debo modificar el valor de retorno para que sea Singleton * volátil?

Editar: Aparentemente const_cast<> eliminará el calificador volátil.Gracias de nuevo a Josh.

¿Fue útil?

Solución

Si está utilizando Visual C ++ 2005/2008, puede usar el patrón de bloqueo doblemente verificado, ya que " las variables volátiles se comportan como vallas & Quot ;. Esta es la forma más eficiente de implementar un singleton con inicio lento.

De 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 acceso al singleton, simplemente llame a GetSingleton (). La primera vez que se llama, el puntero estático se inicializará. Después de que se inicializa, la comprobación NULL evitará el bloqueo solo por leer el puntero.

NO use esto en cualquier compilador, ya que no es portátil. La norma no garantiza cómo funcionará esto. Visual C ++ 2005 se suma explícitamente a la semántica de volátil para que esto sea posible.

Deberá declarar e inicializar la SECCIÓN CRÍTICA en otra parte del código. Pero esa inicialización es barata, por lo que la inicialización diferida generalmente no es importante.

Otros consejos

Una forma sencilla de garantizar la inicialización segura de subprocesos multiplataforma de un singleton es realizarlo explícitamente (mediante una llamada a una función miembro estática en el singleton) en el subproceso principal de su aplicación < strong> antes su aplicación inicia cualquier otro hilo (o al menos cualquier otro hilo que acceda al singleton).

Garantizar el acceso seguro de subprocesos al singleton se logra de la manera habitual con mutexes / secciones críticas.

Inicialización diferida también se puede lograr utilizando un mecanismo similar. El problema habitual encontrado con esto es que el mutex requerido para proporcionar seguridad de subprocesos a menudo se inicializa en el singleton mismo, lo que simplemente empuja el problema de seguridad de subprocesos a la inicialización de la sección mutex / crítica. Una forma de superar 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 mediante una llamada a una función miembro estática. La inicialización de peso pesado del singleton puede ocurrir de una manera segura para subprocesos usando 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, hay buenas razones para evitar el uso de singletons por completo (y por qué a veces se los conoce como antipatrón ):

  • Son esencialmente variables globales 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 crea e inicializa una única instancia de una clase en el hilo principal y la pasa a los objetos que la requieren. Este enfoque puede volverse 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 cuando sea necesario.

Si bien me gusta la solución aceptada, acabo de encontrar otra pista prometedora y pensé que debería compartirla aquí: Inicialización única (Windows)

Puede usar un sistema operativo primitivo como mutex o sección crítica para garantizar una inicialización segura de subprocesos, sin embargo, esto generará una sobrecarga cada vez que se acceda a su puntero singleton (debido a la adquisición de un bloqueo). Tampoco es portátil.

Hay un punto de aclaración que debe tener en cuenta para esta pregunta. ¿Requiere ...

  1. Esa única instancia de una clase se crea realmente
  2. Se pueden crear muchas instancias de una clase, pero solo debe haber una verdadera instancia definitiva de la clase

Hay muchos ejemplos en la web para implementar estos patrones en C ++. Aquí hay un Muestra de proyecto de código

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 debe decidir es si desea una inicialización diferida o no. La inicialización diferida significa que el objeto contenido dentro del singleton se crea en la primera llamada ex:

MySingleton::getInstance()->doWork();

si esa llamada no se realiza hasta más tarde, 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 asume que sería seguro para subprocesos, entonces ya no es una inicialización perezosa, requerirá " some " mayor potencia de procesamiento cuando se inicia su aplicación. Sin embargo, resolverá muchos dolores de cabeza sobre las condiciones de la carrera si lo hace.

Si está buscando una solución más portátil y más fácil, podría recurrir para impulsar.

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 perezosa o no. Como muchas respuestas asumen eso, supongo que para la primera frase discuta:

Dado el hecho de que el lenguaje en sí mismo no es consciente del hilo, 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), vea " C ++ y los peligros del bloqueo de doble verificación " por Scott Meyers y Andrei Alexandrescu.

He visto que muchas de las respuestas recurren a sincronizar objetos en la plataforma de Windows usando CriticalSection, pero CriticalSection solo es seguro para subprocesos cuando todos los subprocesos se ejecutan en un solo procesador, hoy probablemente no sea cierto.

MSDN cite: " Los subprocesos 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 aún 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 solo proceso.

Ahora, si " construcción perezosa " no es un requisito, la siguiente solución es segura tanto para módulos cruzados como 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 todos los módulos porque utilizamos un objeto estático con ámbito local en lugar de un objeto global con ámbito de espacio de nombres / archivo.

Es seguro para subprocesos porque: X_singleton_helper debe asignarse al valor correcto antes de ingresar main o DllMain. No es una construcción perezosa también debido a este hecho), en esta expresión la coma es un operador, no una puntuación.

Usa explícitamente " 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 " ¿Scott Meyer se llama " parte de inicio de subproceso único " en " Efectivo C ++ 3rd " elemento 4.

Sin embargo, no estoy muy seguro de si el compilador puede optimizar la llamada a get_X_instance () según el estándar de idioma, comente.

Hay muchas maneras de hacer una inicialización Singleton * segura para subprocesos en Windows. De hecho, algunos de ellos son incluso multiplataforma. En el hilo SO al que se vinculó, buscaban un Singleton que se construye perezosamente en C, que es un poco más específico y puede ser un poco más complicado de hacer bien, dadas las complejidades del modelo de memoria con el que está trabajando .

  • que nunca deberías usar
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top