2010-04-04 14 views
56

El patrón habitual de una clase Singleton es algo así comoSingleton flujos seguros eficiente en C++

static Foo &getInst() 
{ 
    static Foo *inst = NULL; 
    if(inst == NULL) 
    inst = new Foo(...); 
    return *inst;  
} 

Sin embargo, es mi entendimiento de que esta solución no es hilo de seguridad, ya que 1) el constructor de Foo podría llamarse más más de una vez (que puede o no importar) y 2) inst puede no estar completamente construido antes de que se devuelva a un hilo diferente.

Una solución es envolver un mutex alrededor de todo el método, pero luego estoy pagando por la sobrecarga de sincronización mucho después de que realmente lo necesite. Una alternativa es algo así como

static Foo &getInst() 
{ 
    static Foo *inst = NULL; 
    if(inst == NULL) 
    { 
    pthread_mutex_lock(&mutex); 
    if(inst == NULL) 
     inst = new Foo(...); 
    pthread_mutex_unlock(&mutex); 
    } 
    return *inst;  
} 

Es esta la forma correcta de hacerlo, o hay cualquier escollo que debería tener en cuenta? Por ejemplo, ¿hay algún problema de orden de inicialización estática que pueda ocurrir, es decir, siempre se garantiza que el valor sea NULL la primera vez que se llama a getInst?

+5

Pero ¿No tienes tiempo para encontrar un ejemplo y emitir un voto cercano? Estoy recién salido en este momento. – bmargulies

+1

posible duplicado de http://stackoverflow.com/questions/6915/thread-safe-lazy-contruction-of-a-singleton-in-c – kennytm

+3

@bmargulies No, obviamente no se pudo molestar al interrogador, entonces ¿por qué debería ¿YO? He decidido renunciar a la votación negativa y al cierre como una estafa, ya que parezco ser uno de los pocos que se molestan en mantener a raya a SO. Y sabes, la pereza se siente bien! –

Respuesta

35

Su solución se llama 'bloqueo doble comprobación' y la forma en que has escrito no es multi-hilo.

Este Meyers/Alexandrescu paper explica por qué, pero ese documento también es ampliamente malentendido. Comenzó con el meme de "bloqueo comprobado doble no es seguro en C++", pero su conclusión real es que el bloqueo comprobado doble en C++ se puede implementar de forma segura, solo requiere el uso de barreras de memoria en un lugar no obvio.

El documento contiene pseudocódigo que demuestra cómo utilizar las barreras de memoria para implementar de forma segura el DLCP, por lo que no debería ser difícil para usted corregir su implementación.

+0

if (inst == NULL) {temp = new Foo (...); inst = temp;} ¿Eso no garantiza que el constructor haya terminado antes de asignar inst? Me doy cuenta de que puede (y probablemente será) optimizado, pero lógicamente eso resuelve el problema, ¿no? – stu

+0

Eso no lo hace No ayuda porque un compilador compatible es libre de reordenar la tarea y los pasos de construcción como lo considere oportuno. –

+0

Leo el documento con cuidado, y parece que la recomendación es simplemente evitar el DLCP con Singleton. para volatilizar a toda la clase y agregar barreras de memoria (¿no afectaría eso también a la eficiencia?). Para necesidades prácticas, use un simple candado simple y guarde en caché el objeto que obtiene de "GetInstance". – guyarad

8

Utilice pthread_once, lo que garantiza que la función de inicialización se ejecuta de forma atómica.

(En Mac OS X que utiliza un bloqueo de bucle. No sé la aplicación de otras plataformas.)

2

TTBOMK, la única manera segura para los subprocesos garantizado para hacer esto sin bloqueo sería para inicializar todos sus singletons antes de alguna vez comienzas un hilo.

0

Su alternativa se llama "double-checked locking".

Podría existir modelos multi-hilo de memoria en las que trabaja, pero POSIX no garantiza una

0

La implementación de singleton de ACE utiliza un patrón de bloqueo de doble verificación para la seguridad del hilo, puede consultarlo si lo desea.

Puede encontrar el código fuente here.

61

Si está utilizando C++ 11, aquí es una forma correcta de hacer esto:

Foo& getInst() 
{ 
    static Foo inst(...); 
    return inst; 
} 

De acuerdo con la nueva norma no hay necesidad de preocuparse por este problema más. La inicialización de objetos se realizará solo por un hilo, otros hilos esperarán hasta que se complete. O puede usar std :: call_once.(Más información here)

+1

Esta es la solución C++ 11 Esperaría que las personas implementaran. – Alex

+8

Lamentablemente, esto no es seguro para subprocesos en VS2013, consulte "Estática mágica" aquí: http://msdn.microsoft.com/en-gb/library/hh567368.aspx –

+3

VS 14 parece solucionar esto - http: // blogs.msdn.com/b/vcblog/archive/2014/06/03/visual-studio-14-ctp.aspx –

7

Herb Sutter talks about the double-checked locking in CppCon 2014.

A continuación se muestra el código que he implementado en C++ 11, basándose en que:

class Foo { 
public: 
    static Foo* Instance(); 
private: 
    Foo() {} 
    static atomic<Foo*> pinstance; 
    static mutex m_; 
}; 

atomic<Foo*> Foo::pinstance { nullptr }; 
std::mutex Foo::m_; 

Foo* Foo::Instance() { 
    if(pinstance == nullptr) { 
    lock_guard<mutex> lock(m_); 
    if(pinstance == nullptr) { 
     pinstance = new Foo(); 
    } 
    } 
    return pinstance; 
} 

también se puede comprobar programa completo aquí: http://ideone.com/olvK13

+1

¿Por qué implementar la verificación doble cuando ya tienes C++ 11? – Etherealone

+1

@Etherealone ¿cuál es tu sugerencia? – qqibrow

+1

Un simple 'static Foo foo;' y 'return &foo;' dentro de la función de instancia sería suficiente; la inicialización 'estática' es thread-safe en C++ 11. Sin embargo, prefiera la referencia a los apuntadores. – Etherealone