2009-05-21 5 views
24

Ich habe mehrere Artikel und Beiträge gelesen, die sagen, lock(this), lock(typeof(MyType)), lock("a string") sind alle schlechte Praxis, weil ein anderer Thread auf den gleichen Schlüssel sperren und einen Deadlock verursachen könnte. Um dieses Problem zu verstehen, habe ich versucht, einen Beispielcode zu erstellen, um den Deadlock zu veranschaulichen, aber ich war nicht in der Lage, meinen Kopf darum zu wickeln.Beispielcode zur Veranschaulichung eines Deadlocks mithilfe von lock (this)

Kann jemand einen kurzen Code schreiben, der dieses klassische Problem veranschaulicht? Bitte halten Sie es kurz, ich kann Code nur in kleineren Stücken verarbeiten.

Bearbeiten: Ich denke, Lassevk fasst es gut zusammen; dass das eigentliche Problem darin besteht, dass Sie die Kontrolle über Ihre Schlösser verloren haben. Sobald dies der Fall ist, können Sie die Reihenfolge, in der die Sperren aufgerufen werden, nicht mehr kontrollieren und eine mögliche Deadlock-Situation zulassen.

lock(this), lock(typeof(MyType)), usw. sind alle Situationen, in denen Sie eine Sperre gewählt haben, die unmöglich zu kontrollieren ist.

Antwort

31

Ein Deadlock tritt nur dann auf, wenn Sie mehr als eine Sperre haben. Sie benötigen eine Situation, in der beide Threads eine Ressource enthalten, die der andere benötigt (was bedeutet, dass mindestens zwei Ressourcen vorhanden sein müssen und die beiden Threads versuchen müssen, sie in einer anderen Reihenfolge zu erhalten).

So ein einfaches Beispiel:

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

beiden Fäden Unter der Annahme, innerhalb einer Sekunde von der jeweils anderen gestartet werden, werden sie beide haben Zeit, um die erste Sperre zu ergreifen, bevor jemand auf die innere Sperre erhält. Ohne den Aufruf Sleep() hätte einer der Threads höchstwahrscheinlich Zeit, beide Sperren zu erhalten und wieder freizugeben, bevor der andere Thread überhaupt gestartet wurde.

+2

Aha, ich schrieb das gleiche Beispiel, während Sie es gepostet :) aber ich habe lange und int gewählt – Maghis

+2

Sehr gutes Beispiel: Ein Schlüsselelement beim Erstellen eines Deadlocks ist Sperren auf zwei Ressourcen in ** VERSCHIEDENEN ** Bestellungen. –

+0

Ja, das ist der Grundsatz, nach dem Techniken wie "leveled lock" gebaut werden und warum sie sehr gut sind, um Deadlocks zu vermeiden. – Maghis

3

Sicher, hier gehst du.

Beachten Sie, dass das häufigste Beispiel für einen Deadlock ist, wenn Sie mehrere Sperren erhalten und zwei oder mehr Threads auf einander warten.

Zum Beispiel zwei Threads, die wie folgt verriegelt:

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. 

jedoch in diesem Beispiel ich damit nicht gestört, ich Sperre auf unbestimmte Zeit nur ein Thread lassen. Sie wollen wirklich nicht die Kontrolle über Ihre Sperren verlieren, also während dies ein künstliches Beispiel ist, ist die Tatsache, dass der Hintergrund-Thread den Haupt-Thread vollständig blockieren kann, schlecht.

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

Kommentare zu den anderen Antworten Angenommen, ein "Deadlock" ist das, was ich im Szenario mit mehreren Sperren beschrieben habe, aber das Hauptproblem besteht darin, dass Sie die Kontrolle über Ihre Sperren verloren haben, nicht, ob es sich um einen echten Deadlock handelt oder nicht. Du willst nie die Kontrolle über deine Schlösser verlieren. –

+0

Wird ein Thread, der auf unbestimmte Zeit gesperrt ist, als Deadlock-Situation betrachtet? –

-1

Das Problem ist, dass Sperre ("eine Zeichenfolge") auf einem Singleton gesperrt ist. Dies bedeutet, dass andere Objekte, die dieselbe Sperre verwenden, eine unendliche Wartezeit sein können.

zum Beispiel:

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 diesem Fall erzeugt der anruf Code eine Sperre auf einer Zeichenfolge dann einen Arbeiter Objekt macht. Das Worker-Objekt in Work() sperrt die gleiche Zeichenfolge, die in C# ein Singleton ist. Es endet in einem Deadlock, weil der Anrufer die Sperre besitzt und auf ein Signal wartet, das niemals kommen wird.

+1

Das wird nicht Deadlock, weil der gleiche Thread versucht, beide Locsk auszuführen, also gibt es keine Konkurrenz. Wenn ob.Work() in einem anderen Thread ausgeführt wurde, würde es immer noch nicht blockieren, weil früher oder später die erste Sperre freigegeben würde. Beide Threads müssen blockiert werden, bevor sie auf einen Deadlock warten. – jalf

+0

Korrigieren - Aktualisierter Code. – plinth

1

Dies ist ziemlich Standard-Bad-Ness. Die Schlösser außer Betrieb nehmen und dann mit dem Schloss schlafen. Zwei schlechte Dinge zu tun.:)

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

Die Idee ist, dass Sie nie auf etwas sperren sollten Sie nicht kontrollieren können, wer Zugriff hat.

Typobjekte sind Singletons, die für jedes .net-Codeelement sichtbar sind, und Sie können nicht steuern, wer Ihr "This" -Objekt von außen sperrt.

Das gleiche gilt für Strings: Da Strings unveränderlich sind, behält das Framework nur eine Instanz von "hartcodierten" Strings und fügt sie in einen Pool ein (der String soll interniert sein), wenn Sie zweimal in Ihrem schreiben code die string "hallo", du bekommst immer das gleiche abject.

Betrachten wir folgendes Beispiel: Sie gerade Thread1 in Ihrem Super Privatgespräch schrieb, während Thread2 durch eine Bibliothek aufgerufen wird Ihnen in einem Hintergrund-Thread verwenden ...

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