2011-01-30 15 views
20

He escrito una prueba de lo que creo que debería ser un caso válido para un punto muerto. Parece que una vez que el lock ha sido adquirido por una instancia de la clase a, esa instancia ya no necesita readquirir el lock incluso si explícitamente intento llamar a otro método que debería volver a lock.Se requiere bloqueo y otros intentos de bloqueo no bloquean: ¿los bloqueadores C# son reentrantes?

Aquí es la clase:

internal class Tester 
{ 
    private readonly object _sync = new object(); 

    public Tester() { } 

    public void TestLock() 
    { 
     lock (_sync) 
     { 
      for (int i = 0; i < 10; i++) 
      { 
       Deadlock(i); 
      } 
     } 

    } 

    private void Deadlock(int i) 
    { 
     lock (_sync) 
     { 
      Trace.WriteLine(i + " no deadlock!"); 
     } 
    } 
} 

Salida:

0 sin punto muerto!
1 ¡no hay punto muerto!
2 ¡sin punto muerto!
3 ¡sin punto muerto!
4 ¡sin punto muerto!
5 ¡sin punto muerto!
6 ¡sin punto muerto!
7 ¡sin punto muerto!
8 ¡sin punto muerto!
9 ¡no hay punto muerto!

Hubiera pensado que esto podría causar un punto muerto ... ¿Alguien puede arrojar algo de luz sobre esto?

Respuesta

41

Las cerraduras en .NET son reentrantes. Solo las adquisiciones de otros hilos están bloqueadas. Cuando el mismo hilo bloquea el mismo objeto varias veces, simplemente incrementa un contador y lo disminuye cuando se libera. Cuando el contador llega a cero, el bloqueo es en realidad liberado para el acceso de otros hilos.

1

En su escenario, tiene un bloqueo dentro de otro bloqueo. Una vez que el código golpea el bloqueo anidado en "Punto muerto", el código "bloqueo (...)" se ignora esencialmente porque ya lo ha adquirido en "TestLock".

Gran fuente para enhebrar: http://www.albahari.com/threading/part2.aspx.

+1

Estoy bastante cómodo con el multihilo, pero supongo que nunca me di cuenta de que los bloqueos C# son reentrantes. Gracias por la respuesta ... – Kiril

13

Las clases Monitor, Mutex y ReaderWriterLock mantienen bloqueos que tienen afinidad de subprocesos. La clase ReaderWriterLockSlim le permite elegir, tiene un constructor que toma un valor de LockRecursionPolicy. Usar LockRecursionPolicy.NoRecursion es una optimización, bastante grande si su bloqueo es realmente fino.

La clase de semáforo es una clase de sincronización que no tiene ninguna afinidad de subprocesos. Este código interbloqueos de forma fiable:

class Tester { 
    private Semaphore sem = new Semaphore(1, 1); 
    public void TestLock() { 
     sem.WaitOne(); 
     for (int i = 0; i < 10; i++) Deadlock(i); 
     sem.Release(); 
    } 

    private void Deadlock(int i) { 
     if (!sem.WaitOne(100)) Console.WriteLine("deadlock!"); 
     else { 
      sem.Release(); 
      Console.WriteLine("No deadlock!"); 
     } 
    } 
} 

En general, las clases de sincronización afines hilo requieren dos hilos y dos cerraduras a un punto muerto. El patrón estándar es para que un hilo adquiera los bloqueos A y B, para que el otro adquiera B y A. El orden es importante.

Existen escenarios de deadlocks menos obvios en la programación .NET, inducidos por bloqueos que no se pueden ver porque están incorporados en el código .NET framework. Una muy clásica es para BackgroundWorker. Puede escribir código en el subproceso de interfaz de usuario que gira en la propiedad Ocupado, esperando a que se complete el BGW. Eso siempre se bloquea cuando BGW tiene un controlador de eventos RunWorkerCompleted. No puede ejecutarse hasta que el subproceso de la interfaz de usuario permanezca inactivo, la propiedad Ocupado del BGW no será falsa hasta que el controlador de eventos haya terminado de ejecutarse.

+0

gracias por la gran información! – Kiril