Laden...

Auflösen einer Deadlocksituation

Erstellt von Kabelsalat vor 17 Jahren Letzter Beitrag vor 16 Jahren 4.255 Views
Kabelsalat Themenstarter:in
369 Beiträge seit 2006
vor 17 Jahren
Auflösen einer Deadlocksituation

Einen schönen Sonntag allerseits,

Bereits seit einiger Zeit verwende ich in meinen Projekten die Monitor-Klasse bzw. das C# lock-Schlüsselwort nicht mehr direkt. Um die Threadsynchronisierung einheitlicher und auch flexibler zu gestalten habe ich auf Basis der Monitor Klasse ein "LockObject" implementiert. Dieses Unterstützt genauso wie auch die Monitor-Klasse eine Lock-Methode, die ein IDisposable Objekt zurückgibt und somit auch das using-Konstrukt unterstützt. Mir geht es hierbei jedoch um eine Erweiterung dieser Klasse, sodass Deadlocks erkannt werden. Dazu habe ich mir auch bereits einen Ansatz ausgedacht, der soweit funktionieren müsste (implementiert ist er noch nicht). Sorgen mache ich mir um das Auflösen einer erkannten Deadlocksituation.

Angenommen dieser einfache Fall tritt ein:

Thread 2 besitzt das Lock für LockObject B und will das Lock für A beziehen. Dieses wird momentan von Thread 1 gehalten. Bis hier hin existiert noch kein Deadlock, nun will aber Thread 1 auch noch ein Lock für B beziehen (in der Skizze gestrichelt) - die Verklemmung ist geboren.

Mein Algorithmus wird das entstehende Deadlock erkennen und zunächst ein statisches DeadlockDetected event auslösen. Dieses event ist wichtig, da ein Deadlock bekanntermaßen auf einen Designfehler hinweist und daher geloggt werden sollte. Dies möchte ich jedoch der Anwendung überlassen da der Code Bestandteil eines von mehreren Anwendungen genutzten Frameworks ist und ich keinen Logger vorgeben möchte.

Neben dem Auslösen des Ereignisses sollte allerdings auch noch weitergehend reagiert werden, um eine Hängen der Anwendung zu vermeiden und die Situation zu klären. Dazu werde ich wohl im auslösenden Thread (hier 1) eine Exception werfen und das Lock auf A freigeben. Dieser Schritt bringt allerdings weitreichende Nebenwirkungen mit sich, da bei Nichtbehandlung dieser Exception, die durch A geschützten Ressourcen in einem inkonsitenten Zustand verbleiben. Daher sollten in diesem Fall auch alle weiteren Threads, die diese Ressourcen nutzen möchten benachrichtigt werden, aber wie? In diesem Fall auch in Thread 2 die Exception auslösen? Beenden der Anwendung?

... wie würdet ihr diesesn Fall angehen?

Vielen Dank für eure Unterstützung

Kabelsalat

T
512 Beiträge seit 2006
vor 17 Jahren

Exception ist das einzige Sinnvolle was du machen kannst.
Du musst einfach davon ausgehen, dass beim Auftreten der Exception das Richtige gemacht wird.

Ich meine man lockt ja nicht aus Spaß. Man macht in dem Moment Änderungen an einem Objekt, was die anderen noch nicht so sehen sollen. Wenn ein Deadlock entsteht muss man die Änderungen zurücknehmen, anders geht es einfach nicht.

e.f.q.

Aus Falschem folgt Beliebiges

Kabelsalat Themenstarter:in
369 Beiträge seit 2006
vor 17 Jahren

Das habe ich mir auch gedacht. Immerhin kann unter .Net 2 nichts schlimmes passieren: Die Exception beendet wenn sie nicht behandelt wird den Prozess, wohingegen sie in .Net 1(.1) in vielen Fällen automatisch abgefangen wurden, was gerade in diesem Zusammenhang gravierende Folgen haben könnte...

49.485 Beiträge seit 2005
vor 17 Jahren

Hallo Kabelsalat,

Deadlocks der beschriebene Art kann man übrigens vermeiden, wenn man eine feste Reihenfolge vorgibt, in der die LockObjekte gesperrt werden müssen, also z.B. immer erst A, dann B. Wenn sich da jeder Thread dran hält (du sagst ja selbst, dass es eine Designfrage ist), kann kein Deadlock auftreten. Wenn einer aufritt, sehe auch ich nur eine Exception als einzige Lösung.

herbivore

Kabelsalat Themenstarter:in
369 Beiträge seit 2006
vor 17 Jahren

Es geht darum, dass solche Designfehler trotz aller Rücksicht auftreten - bloß zu Erkennen sind sie im laufenden Programm sehr schwer, da sie je nach Zusammenhang bloß sporadisch zuschlagen. Daher auch meine DeadlockErkennung: Es geht darum die Anwendung in einem solchen Fall noch anständig beenden zu können. Fast noch wichtiger ist aber die eindeutige Identifizierung und Lokalisierung des Fehlers (auch im Endprodukt) - diese Informationen sind mit unter Gold wert.

49.485 Beiträge seit 2005
vor 17 Jahren

Hallo Kabelsalat,

ich habe nichts Gegenteiliges gesagt. Ich sehe das ganz genauso.

herbivore

Kabelsalat Themenstarter:in
369 Beiträge seit 2006
vor 17 Jahren

Vielleicht könnt ihr mir bei der Gestaltung des DeadlockDetected Events weiterhelfen: Ich bin mir nicht ganz im klaren darüber, welche Informationen die EventArgs zur Verfügung stellen sollten.

Der Thread, der das Deadlock hervorruft, wird bereits als sender angegeben. Wahrscheinlich ist es aber sinnvoll diesen nocheinmal explizit in die EventArgs mit aufzunehmen. Auch sollte ich wohl Informationen über alle beteiligten LockObjekte sowie Threads und deren Reihenfolge (also das was oben durch das Diagramm dargestellt ist) aufnehmen, aber wie soll die DeadlockEventArgs Klasse diese darstellen? Was sollte ich sonst noch mit aufnehmen...

49.485 Beiträge seit 2005
vor 17 Jahren

Hallo Kabelsalat,

wenn wir uns einig sind, dass ein Deadlock auf einem Designfehler beruht, sollte es keinen Event, sondern eine Exception geben.

herbivore

Kabelsalat Themenstarter:in
369 Beiträge seit 2006
vor 17 Jahren

Jaein: Die Exception soll auf jeden Fall ausgelöst werden, aber auch das Event ist für Logging-Zwecke von Bedeutung:

Kabelsalat
Mein Algorithmus wird das entstehende Deadlock erkennen und zunächst ein statisches DeadlockDetected event auslösen. Dieses event ist wichtig, da ein Deadlock bekanntermaßen auf einen Designfehler hinweist und daher geloggt werden sollte. Dies möchte ich jedoch der Anwendung überlassen da der Code Bestandteil eines von mehreren Anwendungen genutzten Frameworks ist und ich keinen Logger vorgeben möchte.

... für die Exception stellt sich aber ebenfalls die selbe Frage.

49.485 Beiträge seit 2005
vor 17 Jahren

Hallo Kabelsalat,

auch meiner Sicht ist eine Exception das einzig Richtige und auch für's Logging (mehr als) ausreichend. Immerhin hat man ja in der Exception den ganzen StackTrace. Alle ggf. zusätzlich interessanten Informationen kann man in eine spezialisierte Exception-Klasse aufnehmen. Dazu definiert man in der Exception-Klassen einfach die entsprechenden Properties.

Was sollte ich sonst noch mit aufnehmen...

Da die Exception ja "nur" dafür da ist, den Designfehler zu finden, braucht man auch nur die Informationen, die einem beim lokalisieren des Fehlers helfen. Wenn man StackTrace, ThreadIds/-Namen und LockObjekte hat, sollte das ausreichen.

herbivore

Kabelsalat Themenstarter:in
369 Beiträge seit 2006
vor 17 Jahren

Mir geht es darum, dass das Logging auch dann stattfindet, wenn die Exception behandelt wird. Somit kann ich an kritischen Stellen ein Exceptionhandling für den Ernstfall zur Verfügung stellen und muss dennoch nicht auf das Logging verzichten.

49.485 Beiträge seit 2005
vor 17 Jahren

Hallo Kabelsalat,

du musst auch mit Exceptions nicht aufs Logging verzichten. Events sind aus meiner Sicht hier nicht nur unnötig, sondern schlechtes Design.

herbivore

Kabelsalat Themenstarter:in
369 Beiträge seit 2006
vor 17 Jahren

Aber das hieße ich muss die Exception doch weiterreichen oder "Vorort" beim Behandeln der Exception loggen.

Ich stelle mir das so vor:

Eine Anwendung mit Multi-Thread Unterstütztung fügt dem DeadlockDetected einen Handler hinzu, der das Even in jedem Fall in irgendeiner Form mitschreibt. Darüberhinaus gibt es nocht die Exception, die entweder behandlet wird oder auch nicht, was dann ein Beenden der Anwendung bzw. der AppDomain zusammen mit einem abermaligen loggen, aber diesmal als unbehandlete Exception zur Folge hat.

49.485 Beiträge seit 2005
vor 17 Jahren

Hallo Kabelsalat,

ich gehe davon aus, dass man eine Deadlock-Exception nicht vernünftig behandeln kann, sondern benutzt, um den Designfehler zu finden. Innerhalb der Bibliothek würde man die Exception also nicht fangen und da wo man sie fängt, kann man sie auch problemlos loggen. Ich verstehe also nicht, warum du so beharrlich auf Events hinauswillst. Events halte ich ich hier nicht nur für unnötig, sondern ich würde sie wie gesagt nicht verwenden. Ich käme gar nicht erst auf den Gedanken hier Events einzusetzen.

herbivore

Kabelsalat Themenstarter:in
369 Beiträge seit 2006
vor 17 Jahren

Da ist etwas dran. Allerdings ist es durchaus möglich auf die Exception angemessen zu reagieren, den Zustand zurückzusetzen und die Operation von neuem zu beginnen (nach dem Rücksetzen muss das Lock natürlich zunächst einmal freigegeben werden) . Jedoch kann man wohl dennoch auf das Event verzichten, da ein Loggen bei frühzeitig behandelter Exception wiederum auch keinen Sinn ergibt (in diesem Fall ist das Deadlock wohl in das Design einkalkuliert).

P
48 Beiträge seit 2008
vor 16 Jahren

Hallo Community,

es ist zwar schon länger her, dass hier zu diesem Thema was gesagt wurde, aber die letzten Tage war ich dazu gezwungen eine ähnliche Lösung zum Thema zu finden und es ist tatsächlich möglich in .Net 1.1 Deadlocks zu erkennen und darauf zu reagieren. Dieses setzt allerdings ein wenig Programmieraufwand voraus 🙂

Doch zunächst mein (recht typischer) Fall: ich habe eine Anwendung, welche Grafische Objekte darstellt. Die Darstellung wird über einen Thread geregelt. Dieser holt sich aus einer Liste die Objekte und zeichnet diese per GDI in eine PictureBox. Soweit so gut. Die Grafiken sind allerdings auch durch den Anwender während der Ausführung manipulierbar. Also müssen diese ThreadSafe programmiert werden. Als MultiThread-Neuling hat es natürlich vorn und hinten geknallt und die Anwendung frierte ein. Mittlerweile klappts nun mit MultiThreading, dennoch wollte ich unbedingt die 100%ige Sicherheit haben, dass die Anwendung nicht mehr einfriert.....

Zunächst einmal wird eine neue Klasse gebraucht, welche einen Thread beinhaltet (und mehr). Dieser "ManagedThread" braucht im wesentlichen einige Flags (für Status, Priorität, etc), einen eindeutigen Namen, einen hochgenauen Timer (Stichwort "QueryPerformanceCounter" und "QueryPerformanceFrequency"), eine String-Liste und natürlich den Thread selbst.
Standard: die Thread-Methode läuft in einer while (!Stoppped) Schleife und feuert ein Event, wenn dieses gesetzt ist, hier ein Code-Snipped

 if (itsEvent != null)
         {
            while (!itsIsStopped)
            {
               if (!itsIsPaused)
               {
                  itsTimer.Start();

                  itsEvent();

                  itsWorkTime = itsTimer.Elapsed();
                  itsSleepTime = itsMinTime - itsWorkTime;

                  if (itsSleepTime < 10)
                     itsSleepTime = 10;
               }
               Thread.Sleep(itsSleepTime);
            }
         }

Wichtig dabei ist, dass der Timer bei jedem Aufruf neu gestartet wird. Nach dem der Event abgearbeitet ist, wird die Zeit für den Event gemessen. Diese Zeit wird von der MinimalZeit des Threads abgezogen. Die MinimalZeit ist die Zeit, die dem Thread mindestens zugesprochen wird. Die Berechnung ist wichtig, falls der Event des Threads nicht vom Thread selbst, sondern von der GUI per Invoke ausgeführt werden soll. Genau dann legt sich der Thread länger schlafen und gibt der GUI die Zeit den Event abzuarbeiten. Zudem kann über den Timer die Zeit geprüft werden, wie lange der Thread schon blockiert. Liegt nämlich eine Blockade im Event des Threads vor, wird der Timer nie zurückgesetzt und zählt hoch. Die ist EIN Kriterium ob der Thread verklemmt ist oder nicht. Das zweite Kriterium ist der ThreadState des Thread-Objektes. Dieses muss "WaitSleepJoin" sein. Das dritte Kriterium ist, ob die Threads, die mit dem aktuellen Thread parallel laufen sollen (die Threads, die auf selbe Betriebsmittel zurückgreifen) auch blockiert sind (mindestens einer!). Dem Thread müssen seine "Partner" über die string-Liste bekannt gemacht werden. zb hat Thread1 "T2" in seiner Liste und Thread2 hat "T1" in seiner Liste, wenn beide auf die selben Mittel zurückgreifen. Dies muss leider manuell im Code gemacht werden, ist aber auch net wirklich anstrengend 🙂

Zum Manager: dieser verwaltet die ManagedThreads in einer HashTable (mit Standardzeugs wie Add, Remove, Get realisieren). Interessant ist jetzt der WatchDog. Dieser ist nicht in der HashTable drin, sondern wird gesondert behandelt. Der WatchDog ist wieder ein ManagedThread, welcher bei jeder Ausführung folgendes macht: Wenn eigene Threads existieren (HashTable.Count > 0) dann überprüfe jeden Thread -> wenn der Thread wartet (ThreadState!) und die Wartezeit (Timer!) größer als ein gesetztes Maximum ist (zb 2 Sekunden) dann überprüfe alle "Partner-Threads" seiner Liste. Wenn einer seiner "Partner-Threads" ebenfalls wartet, dann haben wir ein Deadlock von dem aktuellen Thread zu diesem Partner.
Ein Spezialfall stellt der GUI-Thread dar. Denn dieser kann ebenfalls in ein Deadlock geraten, was richtig böse ist. Der GUI-Thread wird umständlich über Invoke geprüft. Der Manager braucht dazu Zugriff auf ein Control (zb Form) der GUI. Soll nun der GUI-Thread überprüft werden, so wird eine Methode per BeginInvoke (NICHT "nur" Invoke) aufgerufen. Diese Methode setzt einen Zähler zurück. Der Zähler wird vor jedem Check der GUI erhöht. Hat dieser Zähler ein Maximum erreicht, so antwortet die GUI nicht mehr und ist anscheinend blockiert.
Wir wissen nun also welche Threads hängen. Um den Deadlock zu lösen, muss nun einer der beiden Threads beendet werden. Dies geht leider nur über Abort, sodass die Methode des Threads abgebrochen und nicht zuende gerechnet wird. Die Entscheidung, welcher der beiden Threads beendet wird, kann zb. über die Priorität gelößt werden.
Nachdem der Thread nun abgeschossen wurde, kann dieser neu instanziiert und wieder gestartet werden.
Die Berechnung, die vor dem Abort lief, muss dann halt nochmal neu gestartet werden, aber wenigstens hängt das System nicht.
Zu beachten gilt: Die Zeit für eine Ausführung des Threads sollte nie größer als das Maximum des Managers (bei dem ein Deadlock vermutet wird). Das Neu-Starten des Threads darf nicht der WatchDog machen. Dies muss man wieder per Invoke der GUI überlassen.

So, vielleicht könnte dies ja dem einen oder anderen weiter helfen 🙂

psy