Pregunta

He sido criado para creer que si varios hilos pueden acceder a una variable, entonces todas las lee y escribe para que la variable debe ser protegido por el código de sincronización, como un "bloqueo" de la declaración, debido a que el procesador podría cambiar a otro hilo a mitad de camino a través de una escritura.

Sin embargo, yo estaba mirando a través del Sistema.Web.De seguridad.La membresía mediante Reflector y encontrar un código como este:

public static class Membership
{
    private static bool s_Initialized = false;
    private static object s_lock = new object();
    private static MembershipProvider s_Provider;

    public static MembershipProvider Provider
    {
        get
        {
            Initialize();
            return s_Provider;
        }
    }

    private static void Initialize()
    {
        if (s_Initialized)
            return;

        lock(s_lock)
        {
            if (s_Initialized)
                return;

            // Perform initialization...
            s_Initialized = true;
        }
    }
}

¿Por qué es el s_Initialized campo de leer fuera de la cerradura?No podía otro hilo estar tratando de escribir al mismo tiempo? Se lee y escribe de variables atómica?

¿Fue útil?

Solución

Para la respuesta definitiva de ir a la especificación.:)

Partición I, Sección 12.6.6 de la CLI spec estados:"Un conformes CLI deberá garantizar que acceder para leer y escribir correctamente alineado ubicaciones de memoria no más grande que el nativo tamaño de palabra es atómica cuando todos los accesos de escritura a una ubicación son del mismo tamaño."

Lo que confirma que s_Initialized nunca será inestable, y que lee y escribe a primitve tipos más pequeños que los de 32 bits son atómicas.

En particular, double y long (Int64 y UInt64) son no garantizado para ser atómicas en una plataforma de 32 bits.Puede utilizar los métodos de la Interlocked clase para proteger a estos.

Además, mientras que las lecturas y escrituras son atómicas, no es una condición de carrera con la suma, la resta, y el incremento y decremento de los tipos primitivos, ya que debe ser leído, operado, y volver a escribir.El enclavamiento de la clase que permite la protección de estos con el CompareExchange y Increment métodos.

Enclavamiento crea una barrera de memoria para evitar que el procesador de la reordenación de las lecturas y escrituras.El bloqueo se crea la única barrera en este ejemplo.

Otros consejos

Esta es una (mala) forma de la doble verificación de bloqueo patrón en el que se no rosca caja en C#!

Hay un gran problema en este código:

s_Initialized no es volátil.Eso significa que escribe en el código de inicialización se puede mover después de s_Initialized se establece en true y otros hilos pueden ver sin inicializar el código, incluso si s_Initialized es verdad para ellos.Esto no se aplica a la implementación de Microsoft del Framework, ya que cada escritura es un volátil de escritura.

Pero también en la implementación de Microsoft, las lecturas de los datos sin inicializar pueden ser reordenados (es decir,prefetched por la cpu), así que si s_Initialized es cierto, la lectura de los datos que deben ser inicializado puede resultar en la lectura de edad, datos sin inicializar debido a la caché de éxitos (es decir.las lecturas son reordenados).

Por ejemplo:

Thread 1 reads s_Provider (which is null)  
Thread 2 initializes the data  
Thread 2 sets s\_Initialized to true  
Thread 1 reads s\_Initialized (which is true now)  
Thread 1 uses the previously read Provider and gets a NullReferenceException

Moviendo la lectura de s_Provider antes de la lectura de s_Initialized es perfectamente legal, ya que no es volátil leer en cualquier lugar.

Si s_Initialized sería volátil, la lectura de s_Provider no se permitiría que se mueven antes de la lectura de s_Initialized y también la inicialización de que el Proveedor no está autorizado a mover después de s_Initialized se establece en true y todo está bien ahora.

Joe Duffy también escribió un Artículo acerca de este problema: Roto variantes en una doble comprobación de bloqueo

Cuelgue sobre-la pregunta que está en el título no es, definitivamente, la verdadera pregunta que Rory está pidiendo.

El titular de la pregunta tiene la simple respuesta de "No", pero esto no ayuda en absoluto, cuando se ve la verdadera pregunta, que yo no creo que nadie se ha dado una respuesta simple a.

La verdadera pregunta Rory le pide que se presenta mucho más tarde y es más pertinente para el ejemplo que él da.

¿Por qué es el s_Initialized campo de leer fuera de la cerradura?

La respuesta a esto es muy simple, aunque completamente ajena a la atomicidad de la variable de acceso.

El s_Initialized campo se lee fuera de la cerradura porque los bloqueos son caros.

Desde el s_Initialized campo es esencialmente "escribir una vez" nunca va a volver un falso positivo.

Es más económico para leer fuera de la cerradura.

Este es un bajo costo actividad con un alta posibilidad de tener un beneficio.

Es por eso que se lee fuera de la cerradura-para evitar pagar el costo de uso de un bloqueo a menos que sea indicado.

Si las cerraduras eran baratos, el código sería más sencillo, y omite que el primer cheque.

(edición:respuesta agradable de rory sigue.Yeh, boolean lee son muy atómica.Si alguien construyó un procesador con no atómica booleano lee, que había destacado en las DailyWTF.)

La respuesta correcta parece ser, "Sí, la mayoría."

  1. La respuesta de juan de referencia de la CLI especificación indica que los accesos a variables no mayor de 32 bits en un procesador de 32 bits son atómicas.
  2. Otra confirmación de la C# spec, la sección 5.5, La atomicidad de las referencias a variables:

    Lee y escribe los siguientes son tipos de datos atómicos:bool, char, byte, sbyte, short, ushort, uint int, float, y los tipos de referencia.Además, lee y escribe de tipos enum con un tipo subyacente en la lista anterior son también atómica.Lee y escribe de otros tipos, incluyendo long, ulong, el doble, y decimales, así como tipos definidos por el usuario, no están garantizados para ser atómica.

  3. El código en mi ejemplo fue parafraseado de la Pertenencia de clase, como está escrito por el ASP.NET equipo, por lo que siempre fue seguro asumir que la forma en que se accede a la s_Initialized campo es correcto.Ahora sabemos por qué.

Editar:Como Thomas Danecker señala, aunque el acceso del campo es atómica, s_Initialized realmente debe estar marcada volátiles para asegurarse de que el bloqueo no está rota por el procesador de la reordenación de las lecturas y escrituras.

La función Initialize es defectuoso.Debe verse más como este:

private static void Initialize()
{
    if(s_initialized)
        return;

    lock(s_lock)
    {
        if(s_Initialized)
            return;
        s_Initialized = true;
    }
}

Sin la comprobación de segunda dentro de la cerradura es posible que el código de inicialización se ejecuta dos veces.Así que el primer cheque es por rendimiento para ahorrar tomar un bloqueo innecesariamente, y la segunda comprobación es para el caso en que un hilo se está ejecutando el código de inicialización, pero aún no se ha establecido la s_Initialized bandera y para un segundo hilo de pasar la primera verificación y estar esperando en la cerradura.

Lee y escribe de las variables no son atómicas.Usted necesidad de utilizar la Sincronización de la Api para emular atómica de lecturas/escrituras.

Para un impresionante referencia sobre este y muchos temas más que ver con la simultaneidad, asegúrese de que usted obtenga una copia de Joe Duffy último espectáculo.Es un ripper!

"Se tiene acceso a una variable en C# una operación atómica?"

Nope.Y no es un C# cosa, ni siquiera es un .net cosa, es un procesador cosa.

DO es el lugar en que Joe Duffy es el chico para ir a para a este tipo de información.Y "entrelazada" es un buen término de búsqueda a utilizar, si estás con ganas de saber más.

"Torn lee" puede ocurrir en cualquier valor cuyos campos se suman a más que el tamaño de un puntero.

@Leon
Entiendo tu punto de vista - la forma en que me he preguntado, y luego comentó, la pregunta le permite tomarse en un par de maneras diferentes.

Para ser claro, yo quería saber si era seguro tener subprocesos simultáneos leer y escribir en un campo booleano sin explícita en el código de sincronización, es decir, es el acceso a un valor booleano (u otros primitivo-typed) variable atómica.

A continuación, utiliza el código de Membresía para dar un ejemplo concreto, sino que introdujo un montón de distracciones, como el doble check de bloqueo, el hecho de que s_Initialized es sólo una vez, y que me comentó el código de inicialización de sí mismo.

Mi mal.

También puede decorar s_Initialized con la palabra clave volatile y prescinden del uso de bloqueo completo.

Eso no es correcto.Te vas a encontrar con el problema de un segundo hilo de pasar la verificación antes de que el primer hilo ha tenido una oportunidad de establecer el indicador a que se traducirá en múltiples ejecuciones del código de inicialización.

Creo que estás preguntando si s_Initialized podría estar en un estado inestable cuando se lea fuera de la cerradura.La respuesta corta es no.Una simple asignación/leer se reducen a un único conjunto de instrucciones que se atómica en cada procesador que puedo pensar.

No estoy seguro de lo que el caso es para la asignación de 64 bits variables, depende del procesador, supongo que no es atómica, pero probablemente es en los modernos procesadores de 32 bits y, ciertamente, en todos los procesadores de 64 bits.La asignación de los complejos de los tipos de valor no será atómica.

Pensé que eran - no estoy seguro de que el punto de la cerradura en su ejemplo, a menos que también está haciendo algo para s_Provider al mismo tiempo, a continuación, el bloqueo no sería garantizar que estas llamadas sucedió juntos.

¿ //Perform initialization comentario cubierta de la creación de s_Provider?Por ejemplo

private static void Initialize()
{
    if (s_Initialized)
        return;

    lock(s_lock)
    {
        s_Provider = new MembershipProvider ( ... )
        s_Initialized = true;
    }
}

De otra manera la propiedad estática-get sólo va a devolver el valor es nulo.

Tal vez Enclavada da una pista.Y lo contrario este yo bastante bien.

Me habría imaginado que su no atómica.

Para hacer que el código siempre trabajo sobre débilmente ordenó arquitecturas, se debe colocar un MemoryBarrier antes de escribir s_Initialized.

s_Provider = new MemershipProvider;

// MUST PUT BARRIER HERE to make sure the memory writes from the assignment
// and the constructor have been wriitten to memory
// BEFORE the write to s_Initialized!
Thread.MemoryBarrier();

// Now that we've guaranteed that the writes above
// will be globally first, set the flag
s_Initialized = true;

La memoria escribe que ocurren en el MembershipProvider constructor y la escritura a s_Provider no están garantizadas a suceder antes de que usted escriba a s_Initialized débilmente ordenó procesador.

Un montón de pensamiento en este hilo es acerca de si algo es atómica o no.Ese no es el problema.El problema es el fin de que el hilo del que escribe son visibles para los otros hilos.En débilmente ordenó arquitecturas, se escribe en la memoria no se producen en orden y QUE es el verdadero problema, que no se si una variable se ajusta dentro del bus de datos.

EDITAR: En realidad, estoy mezclando plataformas en mis declaraciones.En C# el CLR especificación requiere que las escrituras son globalmente visible, en orden (por el uso de costosos instrucciones para cada tienda si es necesario).Por lo tanto, usted no necesita realmente tener esa memoria de la barrera no.Sin embargo, si se C o C++, donde no hay garantía de visibilidad a nivel global existe una orden, y su plataforma de destino puede tener débilmente ordenado de la memoria, y es multiproceso, entonces usted necesita para asegurarse de que los constructores de las escrituras son globalmente visible antes de actualizar s_Initialized, que es probado fuera de la cerradura.

Un If (itisso) { comprobar un booleano es atómica, pero incluso si no se no hay necesidad de bloquear el primer cheque.

Si algún hilo se ha completado la Inicialización, a continuación, será verdad.No importa si varios hilos están comprobando a la vez.Todos ellos reciben la misma respuesta, y, no habrá ningún conflicto.

La segunda comprobación dentro de la cerradura es necesario porque el otro hilo podría haber agarrado la primera cerradura y completado el proceso de inicialización ya.

Lo que estás preguntando es si el acceso a un campo en un método varias veces atómica, a lo que la respuesta es no.

En el ejemplo anterior, al iniciar la rutina es defectuosa, ya que puede resultar en varios de inicialización.Tendrás que comprobar la s_Initialized bandera dentro de la cerradura, así como en el exterior, para evitar una condición de carrera en el que varios subprocesos leer el s_Initialized la bandera antes de que ninguno de ellos hace el código de inicialización.E. g.,

private static void Initialize()
{
    if (s_Initialized)
        return;

    lock(s_lock)
    {
        if (s_Initialized)
            return;
        s_Provider = new MembershipProvider ( ... )
        s_Initialized = true;
    }
}

Ack, no importa...como se ha señalado, esto es, de hecho incorrecto.Esto no impide que un segundo hilo de entrar en la "inicializar" el código de la sección.Bah.

También puede decorar s_Initialized con la palabra clave volatile y prescinden del uso de bloqueo completo.

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