Comportamiento extraño al cambiar un objeto bloqueado de forma exclusiva - Monitor. Ingrese (x)

StackOverflow https://stackoverflow.com/questions/828014

  •  06-07-2019
  •  | 
  •  

Pregunta

Quería ver qué sucedió si cambia la referencia de un objeto bloqueado exclusivamente por Monitor.Enter (). Como era de esperar, se lanzó una SynchronizationLockException. Pero me sorprendió ver varios hilos pasando el Monitor antes de que se lanzara la excepción.

Esto es lo que hace el código siguiente.

  1. crear e iniciar 100 hilos
  2. hacer que todo el hilo espere hasta que se establezca un ManualResetEvent.
  3. configura ManualResetEvent - Un poco como agitar la bandera verde en una carrera de Indy.
  4. establecer un bloqueo exclusivo (Monitor.Enter (x)) en x
  5. cambiar la referencia de x.

En este punto, esperaba que se produjera algún tipo de excepción, pero eso no ocurre hasta que Monitor.Exit (x). Lo que es realmente extraño es que 10 - 20 hilos parecen ser capaces de superar el bloqueo antes de que se levante la excepción. ¿Cómo sucede eso? No parece que debiera. Por supuesto, cambiar la referencia del objeto exclusivamente bloqueado es un no-no. Nunca lo haría en código real, pero aún así me sorprendió ver que los otros hilos pasaban del monitor. ¿Tus pensamientos?

using System;
using System.Threading;

namespace ThreadingPlayground
{
  class Program
  {
    private int _value;
    object x = new object();

    ManualResetEvent _event = new ManualResetEvent(false);

    static void Main()
    {
      Program p = new Program();
      p.StartThreads();
      Console.ReadLine();
    }

    private void StartThreads()
    {

      for(int i = 0;i<100;i++)
      {
        Thread t = new Thread(IncrementValue);
        t.Start();
      }
      _event.Set();
    }

    private void IncrementValue()
    {
      WaitHandle.WaitAll(new WaitHandle[] {_event});

      Monitor.Enter(x);

      // Change the reference of the exclusively locked object. This 
      // allows other threads to enter, should this be possible?
      x = Thread.CurrentThread.ManagedThreadId.ToString();

      Console.WriteLine(++_value);

      // throws a SynchronizationLockException 
      // but not until 10 - 20 more lines are written
      Monitor.Exit(x);
    }
  }
}

 Salida de la consola, parece que algunos hilos pasaron del monitor ??

¿Fue útil?

Solución

Lo que estás viendo es el comportamiento esperado. No hay nada especial en la variable real utilizada para pasar una referencia a Monitor.Enter () . Cambiar la referencia no debe impedir que otros subprocesos adquieran un bloqueo exclusivo, ya que la variable tiene un nuevo valor, y esa referencia no está bloqueada en ninguna parte.

Su problema viene con Salir , porque el hilo que llama Salir no tiene un bloqueo exclusivo en la referencia que se está pasando. Otro hilo puede tener un bloqueo activado no, pero el hilo que está ejecutando no lo hace.

Esto, como saben, es la razón por la que siempre es mejor hacer su bloqueo con una variable cuya referencia nunca cambiará. Si la variable de su recurso puede cambiar, use una nueva referencia.

¿Está esto suficientemente claro?

Otros consejos

'x' es una referencia a un objeto; No es el objeto en sí mismo. Cuando lo hagas

      x = Thread.CurrentThread.ManagedThreadId.ToString();

Ha desechado el objeto bloqueado al que x hizo referencia anteriormente, y ha hecho que x se refiera a otro objeto. Ahora cuando lo hagas

      Monitor.Exit(x);

Obtienes la excepción porque este objeto no está de hecho bloqueado, el objeto que bloqueaste ahora es basura para que el recolector de basura lo recoja.

El motivo de este comportamiento es que estás cambiando el valor de x aquí:

x = Thread.CurrentThread.ManagedThreadId.ToString();

Así que cuando llegas a

Monitor.Exit(x)

estás liberando el bloqueo con un objeto diferente. Es como si pusieras un candado con una llave e intentas quitar el candado con la llave de otro candado.

Además, Console.Writeline es una instrucción costosa en comparación con las demás, por lo que varios subprocesos pueden ingresar al Monitor antes de que uno de ellos se acerque a la línea de meta.

Aquí hay un ejemplo de ejecución:

Comienzas un montón de hilos.

  • Thread T1 adquiere el bloqueo con el objeto x .
  • T2 intenta adquirir un bloqueo con el objeto x . Este hilo no tiene suerte, ya que sabemos que va a esperar por siempre.
  • T1 cambia x . Ahora es un nuevo objeto. Lo llamaré x'1 .
  • T3 intenta adquirir el bloqueo. Pero la variable x en realidad hace referencia al objeto x'1 . Nadie ha bloqueado el x'1 , por lo que pasa.
  • T3 cambia x . Ahora es un nuevo objeto llamado x'3 .
  • T1 escribe en la consola.
  • T3 escribe en la consola.
  • T1 intenta liberar el bloqueo con la variable x , que apunta al objeto ... x'3 .
  • El objeto Monitor dice: " Hey, ¿qué crees que estás haciendo? ¡No tienes esa cerradura! Coma esta excepción poco sucker "
  • Fin
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top