c# - squad - thread sync que es
¿Un enunciado de bloqueo alrededor de múltiples enunciados asegura que todos los cambios sean visibles para otros hilos(siempre que ingresen el mismo mutex)? (2)
Si tiene asignaciones múltiples de variables compartidas dentro de un bloque de código de bloqueo, ¿significa necesariamente que todos estos cambios son visibles inmediatamente para otros hilos, potencialmente se ejecutan en otros procesadores una vez que ingresan una instrucción de bloqueo en el mismo objeto o no existe tal ¿garantías?
Muchos de los ejemplos que aparecen muestran un solo "conjunto" o "obtener" de una variable común y entran en detalles de las barreras de la memoria, pero ¿qué sucede si hay un conjunto más complicado de enunciados dentro? Potencialmente, incluso las llamadas a funciones que hacen otras cosas?
Algo como esto:
lock(sharedObject)
{
x = 10;
y = 20;
z = a + 10;
}
Si este código se ejecuta en otro hilo, que posiblemente se ejecute en otro procesador, ¿ofrece alguna garantía sobre la "visibilidad" del cambio?
lock (sharedObject)
{
if (y == 10)
{
// Do something.
}
}
Si la respuesta es no , ¿tal vez y una explicación de cuándo se harán visibles estos cambios?
Perdóname si estoy entendiendo mal tu pregunta (muy posible); pero creo que estás operando en una combinación confusa de los conceptos de sincronización y visibilidad .
El objetivo de un mutex ("exclusión mutua") es garantizar que dos bloques de código no se ejecuten simultáneamente. Entonces en tu ejemplo, el primer bloque:
lock(sharedObject)
{
x = 10;
y = 20;
z = a + 10;
}
... y el segundo bloque:
lock (sharedObject)
{
if (y == 10)
{
// Do something.
}
}
... nunca se ejecutará al mismo tiempo . Esto es lo que la palabra clave de lock
garantiza para usted.
Por lo tanto, cada vez que su código haya ingresado al segundo bloque, las variables x
, y
y z
deben estar en un estado que sea consistente con una ejecución completa del primero. (Esto supone que en todos los lugares donde acceda a estas variables, se lock
en sharedObject
de la misma manera que en estos fragmentos).
Lo que esto significa es que la "visibilidad" de los cambios intermedios dentro del primer bloque es irrelevante desde la perspectiva del segundo, ya que nunca habrá un momento cuando, por ejemplo, el cambio en el valor x
haya ocurrido pero no en y
o z
.
Un bloque de bloqueo incluye una valla de memoria al inicio y al final (inicio y final del bloque). Esto garantiza que cualquier cambio en la memoria sea visible para otros núcleos (por ejemplo, otros subprocesos que se ejecutan en otros núcleos). En su ejemplo, los cambios a x, y, z en su primer bloque de bloqueo serán visibles para cualquier otro subproceso. "Visible" significa que cualquier valor almacenado en caché en un registro se vaciará a la memoria, y cualquier memoria almacenada en caché en la memoria caché de la CPU se enjuagará en la memoria física. ECMA 334 detalla que un bloque de bloqueo es un bloque rodeado por Monitor.Enter y Monitor.Exit. Además, ECMA 335 detalla que Monitor.Enter "realizará implícitamente una operación de lectura volátil ..." y Monitor.Exit "realizará implícitamente una operación de escritura volátil. Esto significa que las modificaciones no serán visibles para otros núcleos / hilos hasta el final del bloque de bloqueo (después del Monitor.Salir), pero si todo su acceso a estas variables está protegido por un bloqueo, no puede haber acceso simultáneo a dichas variables en diferentes núcleos / hilos de todos modos.
Esto significa que las variables protegidas por una declaración de bloqueo no necesitan ser declaradas como volátiles para que sus modificaciones sean visibles para otros hilos.
Como el código de ejemplo solo contiene una operación que se basa en una única operación atómica compartida (leer y escribir un solo valor en y), puede obtener los mismos resultados con:
try
{
x = 10;
y = 20;
Thread.VolatileWrite(ref z, a + 10);
}
y
if(y == 10)
{
// ...
}
El primer bloque garantiza que la escritura en x es visible antes de la escritura en y y la escritura en y es visible antes de la escritura en z. También garantiza que si las escrituras en x o y fueron almacenadas en caché en la memoria caché de CPUs, ese caché se vaciará a la memoria física (y por lo tanto a cualquier otra cadena) inmediatamente después de la llamada a VolatileWrite.
Si dentro del bloque if(y == 10)
haces algo con y
, deberías volver a utilizar la palabra clave lock
.
Además, lo siguiente sería idéntico:
try
{
x = 10;
y = 20;
Thread.MemoryBarrier();
z = a + 10;
}