2009-05-21 16 views
24

He leído varios artículos y publicaciones que dicen que lock(this), lock(typeof(MyType)), lock("a string") son malas prácticas porque otro hilo podría bloquearse en la misma tecla y provocar un interbloqueo. Para entender este problema, estaba intentando crear un código de muestra para ilustrar el punto muerto, pero no he podido entender esto.Código de muestra para ilustrar un interbloqueo usando lock (this)

¿Alguien puede escribir un código conciso que ilustre este clásico problema? Por favor, mantenlo corto, puedo digerir código en trozos más pequeños solamente.

Editar: creo que resume lassevk él para arriba así; que el verdadero problema es que has perdido el control sobre tus bloqueos. Una vez que eso sucede, no puede controlar el orden en que se llaman los bloqueos, y está permitiendo una posible situación de punto muerto.

lock(this), lock(typeof(MyType)), etc. todas son situaciones en las que ha elegido un bloqueo que es imposible de controlar.

Respuesta

31

Un bloqueo se producirá solo si tiene más de un bloqueo. Necesita una situación donde ambos hilos contengan un recurso que el otro necesita (lo que significa que debe haber al menos dos recursos, y los dos hilos tienen que intentar adquirirlos en un orden diferente)

Así que un ejemplo simple:

// thread 1 
lock(typeof(int)) { 
    Thread.Sleep(1000); 
    lock(typeof(float)) { 
    Console.WriteLine("Thread 1 got both locks"); 
    } 

} 

// thread 2 
lock(typeof(float)) { 
    Thread.Sleep(1000); 
    lock(typeof(int)) { 
    Console.WriteLine("Thread 2 got both locks"); 
    } 
} 

Suponiendo que ambos hilos se inician dentro de un segundo de los demás, van a tener tanto tiempo para tomar la primera cerradura antes de que alguien se pone a la cerradura interior. Sin la llamada Sleep(), uno de los subprocesos probablemente tendría tiempo para obtener y liberar ambos bloqueos antes de que el otro subproceso incluso comenzara.

+2

aha, estaba escribiendo exactamente la misma muestra mientras lo publicabas :) pero he elegido long e int – Maghis

+2

Muy buen ejemplo: un elemento clave en la creación de un interbloqueo está bloqueando dos recursos en ** DIFERENTES pedidos **. –

+0

Sí, ese es el principio sobre el cual se construyen técnicas como el bloqueo nivelado y por qué son muy buenas para evitar interbloqueos. – Maghis

3

Claro, aquí tienes.

Tenga en cuenta que el ejemplo común de un punto muerto es cuando adquiere varios bloqueos, y dos o más hilos terminan esperando el uno al otro.

Por ejemplo, dos hilos que se traba como esto:

Thread 1    Thread 2 
Lock "A"    Lock "B" 
Lock "B"    Lock "A" <-- both threads will stop dead here 
            waiting for the lock to be come 
            available. 

Sin embargo, en este ejemplo no me molesté con eso, sólo permiten bloquear un hilo indefinidamente. Realmente no quieres perder el control sobre tus bloqueos, así que, aunque este es un ejemplo inventado, el hecho de que el hilo de fondo pueda bloquear completamente el hilo principal como este, es malo.

using System; 
using System.Threading; 

namespace ConsoleApplication7 
{ 
    public class Program 
    { 
     public static void Main(string[] args) 
     { 
      LockableClass lockable = new LockableClass(); 
      new Thread(new ParameterizedThreadStart(BackgroundMethod)).Start(lockable); 
      Thread.Sleep(500); 
      Console.Out.WriteLine("calling Reset"); 
      lockable.Reset(); 
     } 

     private static void BackgroundMethod(Object lockable) 
     { 
      lock (lockable) 
      { 
       Console.Out.WriteLine("background thread got lock now"); 
       Thread.Sleep(Timeout.Infinite); 
      } 
     } 
    } 

    public class LockableClass 
    { 
     public Int32 Value1 { get; set; } 
     public Int32 Value2 { get; set; } 

     public void Reset() 
     { 
      Console.Out.WriteLine("attempting to lock on object"); 
      lock (this) 
      { 
       Console.Out.WriteLine("main thread got lock now"); 
       Value1 = 0; 
       Value2 = 0; 
      } 
     } 
    } 

} 
+2

Comentarios a las otras respuestas Indique, correctamente, que un "punto muerto" es lo que describí en el escenario de bloqueos múltiples, pero el principal problema aquí es que ha perdido el control sobre sus bloqueos, no si es un punto muerto real o no. Nunca quieres perder el control sobre tus bloqueos. –

+0

¿Un hilo que está bloqueado indefinidamente se considera como una situación de punto muerto? –

-1

El problema es que el bloqueo ("una cadena") se está bloqueando en un singleton. Esto significa que otros objetos que usan el mismo bloqueo podrían ser una espera infinita.

por ejemplo:

using System; 
using System.Threading; 

namespace ThreadLock 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      lock ("my lock") 
      { 
       ManualResetEvent evt = new ManualResetEvent(false); 
       WorkerObject worker = new WorkerObject(evt); 
       Thread t = new Thread(new ThreadStart(worker.Work)); 
       t.Start(); 
       evt.WaitOne(); 
      } 
     } 
    } 

    class WorkerObject 
    { 
     private ManualResetEvent _evt; 
     public WorkerObject(ManualResetEvent evt) 
     { 
      _evt = evt; 
     } 
     public void Work() 
     { 
      lock ("my lock") 
      { 
       Console.WriteLine("worked."); 
       _evt.Set(); 
      } 
     } 
    } 
} 

En este caso, el código de llamada crea un bloqueo en una cadena y luego hace que un objeto trabajador. El objeto worker en Work() se bloquea en la misma cadena, que es un singleton en C#. Termina en punto muerto porque la persona que llama posee el bloqueo y está esperando una señal que nunca llegará.

+1

Eso no se estancará, porque el mismo hilo está intentando ejecutar ambos locsk, por lo que no hay conflicto. Si ob.Work() se hubiera ejecutado en otro hilo, aún así no se estancaría, porque tarde o temprano se liberaría el primer bloqueo. Ambos hilos deben bloquearse esperando el uno al otro antes de que sea un punto muerto – jalf

+0

Correcto - código actualizado. – plinth

1

Esto es bastante mala calidad estándar. Grabing los bloqueos fuera de servicio y luego dormir con la cerradura. Dos cosas malas que hacer.:)

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Threading; 

namespace DeadLock 
{ 
    public class Program 
    { 
     static void Main(string[] args) 
     { 
      var ddt = new DontDoThat(); 

      ddt.Go(); 
     } 
    } 

    public class DontDoThat 
    { 
     private int _badSharedState = 0; 
     private readonly object _lock1 = new object(); 
     private readonly object _lock2 = new object(); 

     public void Go() 
     { 
      new Thread(BadGuy1).Start(); 
      new Thread(BadGuy2).Start(); 

      Console.WriteLine("Leaving Go!"); 
     } 

     public void BadGuy1() 
     { 
      lock (_lock1) 
      { 
       Thread.Sleep(100); // yeild with the lock is bad 
       lock (_lock2) 
       { 
        _badSharedState++; 
        Console.Write("From Bad Guy #1: {0})", _badSharedState); 
       } 
      } 
     } 
     public void BadGuy2() 
     { 
      lock (_lock2) 
      { 
       lock (_lock1) 
       { 
        _badSharedState++; 
        Console.Write("From Bad Guy #2: {0})", _badSharedState); 
       } 
      } 
     } 
    } 
} 
3

La idea es que nunca se debe fijar en algo que no puede controlar quién tiene acceso a.

Los objetos de tipo son elementos únicos visibles para cada pieza de código .net y no se puede controlar quién se bloquea en el objeto "este" desde el exterior.

Lo mismo ocurre con las cadenas: dado que las cadenas son inmutables, el marco mantiene solo una instancia de cadenas "codificadas" y las coloca en un grupo (se dice que la cadena está internada), si escribe dos veces en su codifique la cadena "hola", siempre obtendrá el mismo abyecto.

Consideremos el siguiente ejemplo: usted escribió simplemente Thread1 en su llamada privada súper, mientras Thread2 es llamado por alguna biblioteca que está utilizando en un subproceso de fondo ...

void Thread1() 
{ 
    lock (typeof(int)) 
    { 
    Thread.Sleep(1000); 
    lock (typeof(long)) 
     // do something 
    } 
} 

void Thread2() 
{ 
    lock (typeof(long)) 
    { 
    Thread.Sleep(1000); 
    lock (typeof(int)) 
     // do something 
    } 
} 
0
class Character 
{ 
    public Character Other; 
    public string Name; 
    private object locker = new object(); 

    public Character(string name) 
    { 
     Name = name; 
    } 

    public void Go() 
    { 
     lock (locker) 
     { 
      Thread.Sleep(1000); 
      Console.WriteLine("go in {0}", Name); 
      Other.Go(); 
     } 
    } 
} 

class Program 
{ 
    static void Main(string[] args) 
    { 
     Character a = new Character("A"); 
     Character b = new Character("B"); 
     a.Other = b; 
     b.Other = a; 

     new Thread(a.Go).Start(); 
     b.Go(); 

     Console.ReadLine(); 
    } 
} 
Cuestiones relacionadas