2011-11-25 16 views
42

yo estaba tratando de lock una variable Boolean cuando me encontré con el siguiente error:¿Por qué no podemos bloquear un tipo de valor?

'bool' no es un tipo de referencia como es requerido por la instrucción lock

Parece que sólo los tipos de referencia son permitido en lock declaraciones, pero no estoy seguro de entender por qué.

Andreas está indicando en su comment:

Cuando [un tipo de valor] objeto se pasa de un subproceso a la otra, se realiza una copia, de manera que los hilos terminan trabajando en 2 objetos diferentes, que es seguro.

¿Es cierto? ¿Eso significa que cuando hago lo siguiente, de hecho estoy modificando dos x diferentes en el xToTrue y el método xToFalse?

public static class Program { 

    public static Boolean x = false; 

    [STAThread] 
    static void Main(string[] args) { 

     var t = new Thread(() => xToTrue()); 
     t.Start(); 
     // ... 
     xToFalse(); 
    } 

    private static void xToTrue() { 
     Program.x = true; 
    } 

    private static void xToFalse() { 
     Program.x = false; 
    } 
} 

(este código solo es claramente inútil en su estado, que es sólo para el ejemplo)


P.S: lo que sé sobre esta cuestión en How to properly lock a value type. Mi pregunta no está relacionada con el cómo pero al por qué.

+0

Su programa no está pasando x entre los hilos que está utilizando una copia compartida. Sin embargo, aún no es seguro para subprocesos ya que no está accediendo a x en el alcance de un bloqueo y x no se declara volátil. Siento que viene otra pregunta: "¿Por qué x en este ejemplo necesita ser volátil?" –

+0

@MartinBrown: Lo sé, y en mi código * real *, uso un candado en un objeto dedicado (como mencioné en mi pregunta, está relacionado con * por qué * y no con * how *). En cuanto a 'volátil ', es [no es necesario cuando se bloquea correctamente] (http://blogs.msdn.com/b/ericlippert/archive/2011/06/16/atomicity-volatility-and-immutability-are-different- part-three.aspx). – Otiel

Respuesta

32

Sólo una suposición aquí ...

pero si el compilador le permite bloquear en un tipo de valor, que terminaría nada de bloqueo en absoluto ... porque cada vez que ha pasado el tipo de valor a la lock , estarías pasando una copia en caja; una copia en caja diferente. Entonces los bloqueos serían como si fueran objetos completamente diferentes. (ya que realmente lo son)

Recuerde que cuando pasa un tipo de valor para un parámetro de tipo object, se coloca en una caja (envuelta) en un tipo de referencia. Esto lo convierte en un objeto nuevo cada vez que esto sucede.

+3

El punto clave es que el tipo de valor se encasillaría en un objeto diferente cada vez (el boxeo no es lo mismo que copiar y creo que para el OP y los futuros lectores vale la pena señalarlo). Ver [mi respuesta] (http://stackoverflow.com/q/8267344/593627) –

+0

Ese es un buen punto. Voy a editar –

+0

El compilador podría crear un tipo de referencia fijo invisible al bloquear tipos de valores. – drowa

15

Se expande a:

System.Threading.Monitor.Enter(x); 
try { 
    ... 
} 
finally { 
    System.Threading.Monitor.Exit(x); 
} 

A pesar de que se compilar, Monitor.Enter/Exit requieren un tipo de referencia debido a que un tipo de valor estaría en caja a una instancia de objeto diferente cada vez para cada llamada a Enter y Exit sería operando en diferentes objetos.

Desde la página de MSDN Enter method:

Uso del monitor para bloquear los objetos (es decir, los tipos de referencia), y no los tipos de valor. Cuando pasa una variable de tipo de valor a Ingresar, se encasilla como un objeto. Si pasa la misma variable a Ingresar nuevamente, se coloca en un recuadro como un objeto separado y el hilo no se bloquea. En este caso, el código que Monitor supuestamente protege no está protegido. Además, cuando pasa la variable a Salir, se crea otro objeto separado. Como el objeto pasado a Exit es diferente del objeto pasado a Enter, Monitor lanza SynchronizationLockException. Para obtener más información, consulte el tema conceptual Monitores.

+1

¿Y por qué 'Monitor.Enter' y' Monitor.Exit' requieren un tipo de referencia? (parece ser la pregunta obvia, ya que eso es realmente lo que el OP es después). – Oded

+0

Lo siento, acaba de editar. –

+0

No cuando lo comprueba el compilador no ... Puede requerir un tipo de referencia para que funcione correctamente, e incluso podría arrojar una excepción si el valor es un tipo de valor encuadrado que no tengo comprobado. Pero compila bien. La verificación de tipo de referencia hecha por el compilador es solo para la instrucción lock(). –

2

Porque los tipos de valor no tienen el bloque de sincronización que la instrucción de bloqueo usa para bloquear un objeto. Solo los tipos de referencia llevan la sobrecarga del tipo info, bloque de sincronización, etc.

Si coloca su tipo de referencia, ahora tiene un objeto que contiene el tipo de valor y puede bloquear ese objeto (lo espero) ya que ahora tiene el sobrecarga adicional que tienen los objetos (un puntero a un bloque de sincronización que se usa para bloquear, un puntero al tipo de información, etc.).Sin embargo, como todo el mundo dice: si coloca un objeto en la casilla, obtendrá un objeto NUEVO cada vez que lo coloque en una caja, de modo que se bloqueará en diferentes objetos cada vez, lo que frustra por completo el objetivo de realizar un bloqueo.

Esto probablemente trabajar (aunque es completamente inútil y no he probado)

int x = 7; 
object boxed = (object)x; 

//thread1: 
lock (boxed){ 
... 
} 
//thread2: 
lock(boxed){ 
... 
} 

Mientras todo el mundo utiliza en caja y el objeto en caja sólo se establece una vez que se conseguiría probablemente bloqueo correcto, ya que están bloqueando el objeto en caja y solo se está creando una vez. NO HAGA esto ... es solo un ejercicio de pensamiento (y quizás ni siquiera funcione, como dije, no lo he probado).

En cuanto a su segunda pregunta: No, el valor no se copia para cada hilo. Ambos subprocesos usarán el mismo booleano, pero no garantiza que los subprocesos vean el valor más nuevo para él (cuando un subproceso establece el valor, es posible que no se vuelva a escribir en la ubicación de memoria inmediatamente, para que cualquier otro subproceso que lea el valor obtenga un resultado "viejo").

+0

Gracias por responder mi segunda pregunta. Eso me sonó extraño que los valores se copian para cada hilo. – Otiel

1

La siguiente es tomado de MSDN: declaraciones

La cerradura (C#) y SyncLock (Visual Basic) se pueden utilizar para asegurarse de que un bloque de código se ejecuta hasta su finalización sin interrupción por otros hilos. Esto se logra al obtener un bloqueo de exclusión mutua para un objeto dado durante la duración del bloque de código.

y

El argumento proporcionado a la palabra clave de bloqueo debe ser un objeto basado en un tipo de referencia, y se utiliza para definir el alcance de la cerradura.

Supongo que esto se debe en parte a que el mecanismo de bloqueo utiliza una instancia de ese objeto para crear el bloqueo de exclusión mutua.

4

Si estás preguntando conceptualmente por qué esto no está permitido, yo diría que la respuesta radica en el hecho de que la identidad de un tipo de valor es exactamente equivalente a su valor (eso es lo que hace que sea un valor tipo).

modo que cualquier persona en cualquier parte del universo hablando de la int4 está hablando de lo mismo - ¿cómo entonces puede ser posible reclamar el acceso exclusivo a fijar en él?

+0

Ese es un punto interesante; particularmente si alguien pregunta, "entonces dado que un tipo de valor se pone en caja cuando se pasa a un parámetro de objeto, ¿por qué no tener bloqueo también acepta tipos de valores específicos además?" ... porque para hacer eso, el bloqueo estaría en el valor, no La variable. Eso sería inútil. +1 –

1

De acuerdo con este MSDN Thread, los cambios en una variable de referencia pueden no ser visibles para todos los subprocesos y pueden terminar usando valores obsoletos, y AFAIK creo que los tipos de valor hacen una copia cuando se pasan entre subprocesos.

Para citar exactamente de MSDN

También es importante aclarar que el hecho de la cesión es atómica no implica que la escritura se observa de inmediato por otros hilos. Si la referencia no es volátil, entonces es posible que otro hilo lea un valor obsoleto de la referencia alguna vez después de que su hilo lo haya actualizado. Sin embargo, la actualización en sí es con garantía de ser atómica (no verá una parte del puntero subyacente actualizado).

+3

El tipo de valor no se copia cuando se utiliza con diferentes subprocesos. Es la misma dirección en memry: la razón por la que podría obtener valores obsoletos es que el valor que estableció en un subproceso podría no volver a escribirse inmediatamente en la ubicación de la memoria; podría guardarse en caché en un registro porque el JIT puede ver su va a ser utilizado de nuevo pronto. –

+0

Gracias, no sé, me encanta SO – Vamsi

0

Creo que este es uno de esos casos donde la respuesta a por qué es "porque un ingeniero de Microsoft lo implementó de esa manera".

El modo de bloqueo funciona bajo el capó creando una tabla de estructuras de bloqueo en la memoria y luego usando los objetos vtable para recordar la posición en la tabla donde se encuentra el bloqueo requerido. Esto da la apariencia de que cada objeto tiene un bloqueo cuando en realidad no es así. Solo aquellos que han sido bloqueados lo hacen. Como los tipos de valores no tienen una referencia, no existe una tabla de almacenamiento para guardar la posición de los bloqueos.

No se sabe por qué Microsoft eligió esta forma extraña de hacer las cosas. Podrían haber hecho del Monitor una clase que tuviste que instanciar. Estoy seguro de que he visto un artículo de un empleado de MS que dice que, en la reflexión, este patrón de diseño fue un error, pero parece que no puedo encontrarlo ahora.

+2

Creo que la razón por la que Microsoft hizo las cosas de esa manera era que un tipo de cerradura instantaneable podría: (1) requerir un finalizador, (2) recursos de fuga si se ingresa pero no se deja , o (3) requieren algún otro soporte de GC para la limpieza si se abandona. Supongo que MS decidió el enfoque n. ° 3, y lo implementó de tal manera que cada objeto de clase tendría el costo implícito por igual, y por lo tanto no había ningún impedimento técnico para permitir el bloqueo de cada objeto de clase. – supercat

25

No se puede bloquear un tipo de valor porque no tiene un registro sync root.

El bloqueo se realiza mediante mecanismos internos CLR y OS que se basan en un objeto que tiene un registro al que solo se puede acceder mediante un único hilo a la vez: raíz de bloque de sincronización. Cualquier tipo de referencia tendría:

  • puntero a un tipo
  • raíz de bloques de sincronización
  • puntero a la instancia de datos en montón
4

Me preguntaba por qué el equipo .Net decidió limitar los desarrolladores y permita que el Monitor funcione solo con referencias. En primer lugar, cree que sería bueno bloquearlo contra un System.Int32 en lugar de definir una variable de objeto dedicada solo para fines de bloqueo, estos casilleros generalmente no hacen nada más.

Pero luego parece que cualquier característica proporcionada por el lenguaje debe tener una fuerte semántica que no solo sea útil para los desarrolladores. Entonces la semántica con tipos de valor es que cada vez que aparece un tipo de valor en el código, su expresión se evalúa a un valor. Entonces, desde el punto de vista semántico, si escribimos `lock (x) 'yx es un tipo de valor primitivo, entonces es lo mismo que diríamos" bloquear un bloque de código crítico contra el valor de la variable x "que suena más que extraño, seguro :). Mientras tanto, cuando conocemos las variables de referencia en el código, pensamos "Ah, es una referencia a un objeto" e implica que la referencia se puede compartir entre bloques de código, métodos, clases e incluso hilos y procesos y, por lo tanto, puede servir como un Guardia.

En dos palabras, las variables de tipo de valor aparecen en el código solo para ser evaluadas a su valor real en cada expresión - nada más.

Supongo que ese es uno de los puntos principales.

+2

+1 Explicado simplemente, y usted también ha respondido @martin brown – dotnetguy

Cuestiones relacionadas