2012-09-06 25 views
11

He leído muchas preguntas teniendo en cuenta el bloqueo comprobado doble seguro para subprocesos (para singletons o init perezoso). En algunos hilos, la respuesta es que el patrón está completamente roto, otros sugieren una solución.C++ 11: seguro de bloqueo comprobado doble para la inicialización perezosa. ¿Posible?

Así que mi pregunta es: ¿hay alguna manera de escribir un patrón de bloqueo comprobado completamente seguro para subprocesos en C++? Si es así, ¿cómo se ve?

Podemos suponer que C++ 11, si eso hace las cosas más fáciles. Por lo que sé, C++ 11 mejoró el modelo de memoria que podría producir las mejoras necesarias.

Sé que es posible hacerlo en Java haciendo que la variable de doble verificación sea volátil. Como C++ 11 tomó prestadas partes grandes del modelo de memoria de Java, entonces creo que podría ser posible, pero ¿cómo?

+5

Si puede usar C++ 11 simplemente ignore todo el negocio de doble bloqueo comprobado y use variables locales estáticas o 'std :: call_once'. –

+0

¿Los locales estáticos se inicializan de forma perezosa? Y sobre 'call_once': ¿Cómo se asegura de que la llamada una vez no escriba la referencia no creada a la variable? – gexicide

+3

sí, los locals estáticos se inicializan perezosamente de una manera segura para subprocesos. Y 'call_once' garantiza que el sujeto solo se llame una vez; y que ninguna otra llamada a 'call_once' regresa antes de que regrese el que realmente ejecuta la función (puede leer más aquí http://en.cppreference.com/w/cpp/thread/call_once). Cómo funciona eso depende de la implementación. Estas dos cosas existen básicamente, por lo que no debe preocuparse por escribir más errores implementaciones de bloqueo comprobado. –

Respuesta

16

Utilice simplemente una variable local estática para Singleton perezosamente inicializados, así:

MySingleton* GetInstance() { 
    static MySingleton instance; 
    return &instance; 
} 

El (C++ 11) estándar ya garantiza que las variables estáticas se inicializan de manera multi-hilo y parece probable que la implementación de esto al menos tan robusto y eficiente como cualquier cosa que usted mismo escriba.

El threadsafety de la inicialización se puede encontrar en §6.7.4 de la (C++ 11) estándar:

Si el control entra en la declaración simultáneamente mientras que la variable se inicializa, la ejecución concurrente deberá espere a que finalice la inicialización.

+11

Esto es una locura. ¿Por qué? ¿Por qué devolverías un puntero si ** nunca ** puede ser 'nulo'? –

+1

@MatthieuM .: Principalmente porque hace que las personas estén menos inclinadas a copiar el objeto subyacente. Por supuesto, un singleton bien diseñado ** no debería ** tener un constructor de copia, pero aún así. Realmente no veo cómo importa el retorno por referencia frente a la devolución por valor en ese caso. Por lo tanto, difícilmente llamaría a eso una locura. – Grizzly

+2

@Grizzly: si un objeto no debe ser copiable, corresponde al objeto hacer cumplir eso. Si un objeto debe tener una instancia que sea accesible globalmente, debe haber una función para manejar eso (como la suya). Estas dos cosas están separadas, y no hay ninguna razón para combinarlas. Esta es la razón por la cual el patrón de Singleton es estúpido. – GManNickG

3

Como quería ver una implementación válida de DCLP C++ 11, aquí tiene una.

El comportamiento es totalmente seguro para subprocesos e idéntico al GetInstance() en la respuesta de Grizzly.

std::mutex mtx; 
std::atomic<MySingleton *> instance_p{nullptr}; 

MySingleton* GetInstance() 
{ 
    auto *p = instance_p.load(std::memory_order_acquire); 

    if (!p) 
    { 
     std::lock_guard<std::mutex> lck{mtx}; 

     p = instance_p.load(std::memory_order_relaxed); 
     if (!p) 
     { 
      p = new MySingleton; 
      instance_p.store(p, std::memory_order_release); 
     } 
    } 

    return p; 
} 
+0

En la práctica, querría declarar sus variables 'mtx' y' instance_p' como variables globales fuera de la función, en lugar de como estáticas dentro de la función, ya que de lo contrario está pagando el precio de las comprobaciones internas del compilador sobre la inicialización de 'mtx' y' instance_p' en _every_ call, derrotando el punto de bloqueo con doble verificación (ya que en lo que respecta a la performance también podrías declarar el singleton como estático en ese momento). – BeeOnRope

+0

@BeeOnRope Punto válido ... He hecho ese cambio. – LWimsey

+0

Es posible que desee agregar que, básicamente, nunca desea escribir este código. La respuesta de Grizzly es más concisa y el compilador podría insertar más magia en el futuro para hacerlo más rápido que este código. – gexicide

Cuestiones relacionadas