Pregunta

Así que he visto un montón de artículos que afirman que ahora el bloqueo comprobado doble C ++, comúnmente usado para prevenir múltiples hilos de intentar inicializar un conjunto unitario con pereza creado, está roto. código de bloqueo comprobado normal doble lee como sigue:

class singleton {
private:
    singleton(); // private constructor so users must call instance()
    static boost::mutex _init_mutex;

public:
    static singleton & instance()
    {
        static singleton* instance;

        if(!instance)
        {
            boost::mutex::scoped_lock lock(_init_mutex);

            if(!instance)           
                instance = new singleton;
        }

        return *instance;
    }
};

El problema parece ser que es la línea de la asignación de instancia - el compilador es libre de asignar el objeto y luego asignar el puntero a ella, o para ajustar la indicación del lugar donde se le asignará, a continuación, asignarlo. El último caso se rompe el idioma - un hilo puede asignar la memoria y asignar el puntero, pero no ejecutar el constructor de la Singleton antes de que sea puesto a dormir - a continuación, el segundo hilo verá que la instancia no es nulo y tratar de devolverlo , a pesar de que no se ha construido aún.

vio una sugerencia utilizar un hilo booleano local y comprobar que en lugar de instance. Algo como esto:

class singleton {
private:
    singleton(); // private constructor so users must call instance()
    static boost::mutex _init_mutex;
    static boost::thread_specific_ptr<int> _sync_check;

public:
    static singleton & instance()
    {
        static singleton* instance;

        if(!_sync_check.get())
        {
            boost::mutex::scoped_lock lock(_init_mutex);

            if(!instance)           
                instance = new singleton;

            // Any non-null value would work, we're really just using it as a
            // thread specific bool.
            _sync_check = reinterpret_cast<int*>(1);
        }

        return *instance;
    }
};

De esta manera cada hilo termina de comprobar si la instancia se ha creado una vez, pero se detiene después de eso, lo que implica un poco de impacto en el rendimiento, pero aún no casi tan malo como bloquear todas las llamadas. Pero lo que si sólo utilizamos un bool estática local:?

class singleton {
private:
    singleton(); // private constructor so users must call instance()
    static boost::mutex _init_mutex;

public:
    static singleton & instance()
    {
        static bool sync_check = false;
        static singleton* instance;

        if(!sync_check)
        {
            boost::mutex::scoped_lock lock(_init_mutex);

            if(!instance)           
                instance = new singleton;

            sync_check = true;
        }

        return *instance;
    }
};

¿Por qué no este trabajo? Incluso si sync_check eran para ser leído por un hilo cuando está siendo asignado en otro el valor de basura seguirá siendo distinto de cero y por lo tanto cierto. Este artículo del Dr. Dobb afirma que hay que cerrar, porque nunca se va a ganar una batalla con el compilador sobre las instrucciones de reordenamiento. Que me hace pensar que esto no tiene que trabajar por alguna razón, pero no puedo entender por qué. Si los requisitos de puntos de secuencia son como se pierde como el artículo del Dr. Dobb me hace creer, no entiendo por qué cualquier código después del bloqueo podría no ser reordenadas que ser antes de la cerradura. Lo que haría C ++ periodo rota multihilo.

Creo que podría ver el compilador que se les permita reordenar específicamente sync_check que ser antes de la cerradura porque es una variable local (y aunque es estática no estamos devolviendo una referencia o puntero a ella) - pero entonces esto podría todavía hay que resolver por lo que es un miembro estático (con eficacia global) en su lugar.

Así es que esto funciona o no? ¿Por qué?

¿Fue útil?

Solución

Su solución no soluciona nada ya que las escrituras en sync_check y la instancia se pueden hacer fuera de servicio de la CPU. A modo de ejemplo se imaginan las dos primeras llamadas a instancia ocurren en aproximadamente el mismo tiempo en dos CPUs diferentes. El primer hilo adquirirá la cerradura, inicializar el puntero y establecer sync_check true, en ese orden, pero el procesador puede cambiar el orden de las escrituras en la memoria. Por otra CPU, entonces es posible que el segundo hilo para comprobar sync_check, ver que es cierto, pero instancia aún no se puede escribir en la memoria. Ver lockless para Xbox 360 y Microsoft Windows para más detalles.

La solución sync_check específica hilo se menciona a continuación, debería funcionar (suponiendo que usted hace funcionar su puntero a 0).

Otros consejos

Hay un gran lectura sobre esto (aunque es .NET / C # orientado) aquí: http://msdn.microsoft.com/en-us/magazine/cc163715.aspx

Lo que se reduce a es que se necesita para ser capaz de decirle a la CPU que no puede cambiar el orden de su lecturas / escrituras para esta variable de acceso (desde el Pentium original, la CPU puede cambiar el orden de ciertas instrucciones si se piensa que la lógica no se verían afectadas), y que necesita para asegurarse de que la memoria caché es consistente (no se olvide de que - nosotros los desarrolladores llegar a pretender que toda la memoria es sólo un recurso plana, pero en realidad, cada núcleo de CPU tiene caché, algunos no compartido (L1), algunos podrían ser compartida veces (L2)) - su initizlization podría escribir en la memoria RAM principal, pero otro núcleo puede tener el valor no inicializado en la memoria caché. Si usted no tiene ningún semántica de concurrencia, la CPU no puede saber que su caché está sucio.

No sé el lado C ++, pero en .NET, que designaría la variable como volátil con el fin de proteger el acceso a la misma (o se usaría el / escritura de los métodos de barrera leer la memoria en System.Threading).

Como acotación al margen, he leído que en .NET 2.0, bloqueo doble comprobación se garantiza que funcione sin variables "volátil" (para cualquier lector .net por ahí) - que no le ayuda con su código C ++ .

Si usted quiere estar seguro, usted tendrá que hacer el C ++ equivalente de una marca como una variable volátil en C #.

"El último caso se rompe el idioma -. Dos hilos podrían terminar creando el singleton"

Pero si entiendo el código correctamente, el primer ejemplo, se comprueba si la instancia ya existe (que podría ser ejecutado por varios subprocesos al mismo tiempo), si no lo hace una escapada hilo de establecer un bloqueo y crea la instancia - sólo un hilo puede ejecutar la creación en ese momento. Todos los otros hilos quedan bloqueados y esperarán.

Una vez que se crea la instancia y la exclusión mutua se desbloquea el siguiente hilo de espera se bloqueará mutex pero no va a tratar de crear nueva instancia debido a que la comprobación fallará.

La próxima vez que se comprueba la variable de instancia que se establece de modo no hay temas tratará de crear nueva instancia.

No estoy seguro sobre el caso en el que un hilo es la asignación de puntero nuevo caso al otro, mientras que otro hilo comprueba la misma variable - pero creo que va a ser manejado correctamente en este caso

.

Me estoy perdiendo algo aquí?

mal, no es seguro sobre el reordenamiento de las operaciones pero en este caso sería estar alterando la lógica para no esperar que suceda -. Pero no soy experto en este tema

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top