Es un bool de lectura / escritura atómica en C #

84

¿Es atómico acceder a un campo bool en C #? En particular, necesito poner un candado alrededor:

class Foo
{
   private bool _bar;

   //... in some function on any thread (or many threads)
   _bar = true;

   //... same for a read
   if (_bar) { ... }
}
dbkk
fuente
1
Sí, pero (posiblemente) también sí. Sí, acceder / configurar un campo bool es atómico, PERO la operación if no lo es (consulte la respuesta de Dror Helper a continuación), por lo que es posible que también necesite un bloqueo.
JPProgrammer

Respuestas:

119

Si.

Las lecturas y escrituras de los siguientes tipos de datos son atómicas: bool, char, byte, sbyte, short, ushort, uint, int, float y tipos de referencia.

como se encuentra en C # Language Spec .

Editar: Probablemente también valga la pena comprender la palabra clave volátil .

Larsenal
fuente
10
El puntero en sí, reasignándolo, es atómico (es decir, Foo foo1 = foo2;
user142350
4
@configurator: La pregunta es qué es exactamente lo que quiere. Es fácil equivocarse en los programas sin bloqueo; así que a menos que realmente lo necesite, es mejor usar un marco más simple (por ejemplo, el TPL). En otras palabras, 'volátil' no es incorrecto, sino un signo de código complicado (es decir, preferiblemente evitado). El OP no ha dicho realmente lo que quiere, solo dudo en recomendar volatile queramos o no.
Eamon Nerbonne
4
Waw. Esta es una redacción peligrosa, para la gente de C ++, atómico significa que cualquier lectura y escritura también está rodeada por la valla de memoria correspondiente. Que seguramente no es el caso en C #. Porque de lo contrario el rendimiento sería horrible ya que es obligatorio para todas las variables <long. Atómico aquí en el lenguaje de C #, parece significar que cuando las lecturas o escrituras eventualmente suceden, se garantiza que nunca estarán en un estado roto . Pero no dice nada sobre cuándo es "eventualmente".
v.oddou
4
Si escribir en int y long son atómicos, ¿cuándo se usa, Interlocked.Add(ref myInt);por ejemplo?
Mike de Klerk
6
@MikedeKlerk La lectura y la escritura son atómicas, pero separadas. i++es igual a i=i+1, lo que significa que haces una lectura atómica, luego una suma y luego una escritura atómica. Otro hilo podría modificarse idespués de la lectura pero antes de la escritura. Por ejemplo, dos subprocesos que funcionan i++simultáneamente en el mismo i pueden leer al mismo tiempo (y, por lo tanto, leer el mismo valor), agregar uno y luego ambos escriben el mismo valor, agregando efectivamente solo una vez. Interlocked.Add evita esto. Como regla general, el hecho de que un tipo sea atómico solo es útil si solo hay un hilo escribiendo pero muchos hilos leyendo.
Jannis Froese
49

Como se indicó anteriormente, bool es atómico, pero aún debe recordar que también depende de lo que quiera hacer con él.

if(b == false)
{
    //do something
}

no es una operación atómica, lo que significa que el valor de b podría cambiar antes de que el hilo actual ejecute el código después de la instrucción if.

Ayudante de Dror
fuente
29

Los accesos bool son de hecho atómicos, pero esa no es toda la historia.

No tiene que preocuparse por leer un valor que está 'escrito de forma incompleta' (no está claro qué podría significar eso para un bool en cualquier caso), pero debe preocuparse por las cachés del procesador, al menos si hay detalles de el tiempo es un problema. Si el hilo n. ° 1 que se ejecuta en el núcleo A tiene su _barcaché y _barse actualiza mediante el hilo n. ° 2 que se ejecuta en otro núcleo, el hilo n. ° 1 no verá el cambio inmediatamente a menos que agregue bloqueo, declare _barcomo volatileo inserte explícitamente llamadas a Thread.MemoryBarrier()para invalidar el valor en caché.

McKenzieG1
fuente
1
"No está claro qué podría significar eso para un bool en cualquier caso" Elementos que existen en un solo byte de memoria atómica porque todo el byte se escribe al mismo tiempo. Frente a elementos como el doble que existen en varios bytes, un byte podría escribirse antes que el otro byte y podría observar la mitad de la ubicación de la memoria escrita.
MindStalker
3
MemoryBarrier () no invalida ningún caché de procesador. En algunas arquitecturas, el procesador puede reordenar las lecturas y escrituras en la memoria principal para mejorar el rendimiento. El reordenamiento puede ocurrir siempre que, desde el punto de vista de un solo hilo, la semántica permanezca igual. MemoryBarrier () solicita al procesador que limite el reordenamiento para que las operaciones de memoria emitidas antes de la barrera no se reordenen de tal manera que terminen después de la barrera.
TiMoch
1
Una barrera de memoria es útil si crea un objeto grueso y cambia una referencia a él que puede leerse desde otros hilos. La barrera garantiza que la referencia no se actualice en la memoria principal antes que el resto del objeto gordo. Se garantiza que otros subprocesos nunca verán la actualización de referencia antes de que el objeto fat esté realmente disponible en la memoria principal. var fatObject = new FatObject(); Thread.MemoryBarrier(); _sharedRefToFat = fatObject;
TiMoch
1

el enfoque que he utilizado, y creo que es correcto, es

volatile bool b = false;

.. rarely signal an update with a large state change...

lock b_lock
{
  b = true;
  //other;
}

... another thread ...

if(b)
{
    lock b_lock
    {
       if(b)
       {
           //other stuff
           b = false;
       }
     }
}

el objetivo era básicamente evitar tener que bloquear repetidamente un objeto en cada iteración solo para verificar si necesitábamos bloquearlo para proporcionar una gran cantidad de información de cambio de estado que ocurre raramente. Creo que este enfoque funciona. Y si se requiere una consistencia absoluta, creo que lo volátil sería apropiado en el b bool.

stux
fuente
4
De hecho, este es un enfoque correcto para el bloqueo en general, pero si los bools son atómicos, entonces es más simple (y más rápido) omitir el bloqueo.
dbkk
2
Sin el bloqueo, el "gran cambio de estado" no se realizará de forma atómica. La cerradura -> establecer | check -> lock -> check enfoque también asegurará que el código "// otro" se ejecute ANTES del código "// otras cosas". Suponiendo que la sección "otro hilo" está iterando muchas veces (que es en mi caso), solo tener que verificar un bool, la mayor parte del tiempo, pero no adquirir un bloqueo (posiblemente contendido), es una gran ganancia de rendimiento
stux
1
Bueno, si tiene lock(), no es necesario volatile.
xmedeko