2009-05-21 7 views
24

Ho letto diversi articoli e post che dicono che lock(this), lock(typeof(MyType)), lock("a string") sono tutte cattive pratiche perché un altro thread potrebbe bloccare la stessa chiave e causare un deadlock. Per capire questo problema, stavo cercando di creare un codice di esempio per illustrare la situazione di stallo, ma non sono stato in grado di comprendere ciò.Esempio di codice per illustrare un deadlock utilizzando il blocco (this)

Qualcuno può scrivere un breve bit di codice che illustra questo problema classico? Per favore, tienilo breve, posso digerire il codice solo in blocchi più piccoli.

Modifica: Penso che lassevk lo riassuma bene; il vero problema è che hai perso il controllo delle tue serrature. Quando ciò accade, non puoi controllare l'ordine in cui vengono chiamati i blocchi e stai consentendo una potenziale situazione di deadlock.

lock(this), lock(typeof(MyType)), ecc. Sono tutte situazioni in cui è stato scelto un blocco impossibile da controllare.

risposta

31

Un deadlock si verifica solo se si dispone di più di un blocco. Hai bisogno di una situazione in cui entrambi i thread contengono una risorsa di cui l'altra ha bisogno (il che significa che ci devono essere almeno due risorse e i due thread devono tentare di acquisirli in un ordine diverso)

Quindi un semplice esempio:

// 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"); 
    } 
} 

Supponendo entrambi i fili vengono avviati entro un secondo di ogni altri, saranno entrambi hanno il tempo di afferrare la prima serratura prima che qualcuno arriva al blocco interno. Senza la chiamata Sleep(), uno dei thread probabilmente avrà il tempo di ottenere e rilasciare entrambi i blocchi prima ancora che inizi l'altro thread.

+2

aha, stavo scrivendo esattamente lo stesso campione mentre lo hai postato :) ma ho scelto long e int – Maghis

+2

Ottimo esempio: un elemento chiave nella creazione di un deadlock è il blocco su due risorse in ** DIFFERENT ** ordini. –

+0

Sì, questo è il principio su cui vengono costruite tecniche come il blocco livellato e perché sono molto efficaci per evitare i deadlock. – Maghis

3

Certo, ecco qua.

Si noti che l'esempio comune di un deadlock si ha quando si acquisiscono più blocchi e due o più thread finiscono per attendere l'altro.

Ad esempio, due fili che blocca simili:

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. 

Tuttavia, in questo esempio non ho disturbato con questo, ho lasciato bloccare indefinitamente un thread. Davvero non vuoi perdere il controllo sui tuoi lucchetti, quindi mentre questo è un esempio forzato, il fatto che il thread in background possa bloccare completamente il thread principale come questo, è sbagliato.

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

Commenti alle altre risposte indica, correttamente, che un "deadlock" è quello che ho descritto nello scenario dei lock multipli, ma il problema principale qui è che hai perso il controllo sui tuoi lucchetti, non che si tratti di un vero deadlock o meno. Non vuoi mai perdere il controllo sulle tue serrature. –

+0

Un thread bloccato a tempo indeterminato viene considerato come una situazione di deadlock? –

-1

Il problema è che il blocco ("una stringa") si blocca su un singleton. Ciò significa che altri oggetti che utilizzano lo stesso blocco potrebbero essere un'attesa infinita.

ad esempio:

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(); 
      } 
     } 
    } 
} 

In questo caso, il codice chiamante crea un blocco su una stringa poi fa un oggetto operaio. L'oggetto worker in Work() si blocca sulla stessa stringa, che è un singleton in C#. Finisce in deadlock perché il chiamante possiede il lucchetto ed è in attesa di un segnale che non arriverà mai.

+1

Questo non si bloccherà, perché lo stesso thread sta tentando di eseguire entrambi i locsk, quindi non c'è contesa. Se ob.Work() era stato eseguito su un altro thread, non si bloccava ancora, perché prima o poi sarebbe stato rilasciato il primo blocco. Entrambi i thread devono essere bloccati in attesa l'uno dell'altro prima che sia un deadlock – jalf

+0

corretto - codice aggiornato. – plinth

1

Questa è una pessima idea. Grabing le serrature fuori ordine e poi a dormire con la serratura. Due cose cattive da fare.:)

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

L'idea è che non si dovrebbe mai bloccare su qualcosa che non si può controllare a chi ha accesso.

Gli oggetti di tipo sono singleton visibili a ogni pezzo di codice .net e non è possibile controllare chi blocca il proprio "questo" oggetto dall'esterno.

La stessa cosa è per le stringhe: poiché le stringhe sono immutabili, la struttura mantiene solo un'istanza di stringhe "codificate" e le inserisce in un pool (la stringa viene detta internata), se si scrivono due volte nel proprio codifica la stringa "ciao", avrai sempre lo stesso abbietto.

consideri il seguente esempio: hai scritto solo Thread1 nella chiamata privata Super, mentre Thread2 è chiamato da qualche libreria che si sta utilizzando in un thread in background ...

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(); 
    } 
} 
Problemi correlati