In letzter Zeit habe ich an Projekten gearbeitet, die häufig Threading verwenden. Ich denke, dass ich in Ordnung bin, sie zu entwerfen. Verwenden Sie so oft wie möglich das zustandslose Design, sperren Sie den Zugriff auf alle Ressourcen, die mehr als einen Thread benötigen usw. Meine Erfahrung in der funktionalen Programmierung hat dazu immens beigetragen.
Wenn ich jedoch den Thread-Code anderer Leute lese, bin ich verwirrt. Ich debugge gerade einen Deadlock und da sich der Codierungsstil und das Design von meinem persönlichen Stil unterscheiden, fällt es mir schwer, mögliche Deadlock-Bedingungen zu erkennen.
Worauf achten Sie beim Debuggen von Deadlocks?
debugging
multithreading
Michael K
quelle
quelle
Antworten:
Wenn die Situation ein echter Deadlock ist (dh zwei Threads halten zwei verschiedene Sperren, aber mindestens ein Thread möchte eine Sperre, die der andere Thread hält), müssen Sie zuerst alle Vorstellungen darüber aufgeben, wie die Threads die Sperrung anordnen. Nimm nichts an. Möglicherweise möchten Sie alle Kommentare aus dem betrachteten Code entfernen, da Sie durch diese Kommentare möglicherweise an etwas glauben, das nicht zutrifft. Es ist schwer genug zu betonen: Nimm nichts an.
Bestimmen Sie anschließend, welche Sperren gehalten werden, während ein Thread versucht, etwas anderes zu sperren. Wenn möglich, stellen Sie sicher, dass ein Thread in umgekehrter Reihenfolge entsperrt wird. Stellen Sie außerdem sicher, dass ein Thread jeweils nur eine Sperre enthält.
Arbeiten Sie die Ausführung eines Threads sorgfältig durch und untersuchen Sie alle Sperrereignisse. Bestimmen Sie bei jeder Sperre, ob ein Thread andere Sperren enthält, und wenn ja, unter welchen Umständen ein anderer Thread, der einen ähnlichen Ausführungspfad ausführt, zum betreffenden Sperrereignis gelangen kann.
Es ist sicherlich möglich, dass Sie das Problem nicht finden, bevor Ihnen die Zeit oder das Geld ausgeht.
quelle
Wie andere gesagt haben ... Wenn Sie nützliche Informationen für die Protokollierung erhalten können, versuchen Sie dies zuerst, da dies am einfachsten ist.
Identifizieren Sie die betroffenen Sperren. Ändern Sie alle Mutex / Semaphore, die für immer auf zeitgesteuerte Wartezeiten warten ... etwas lächerlich Langes wie 5 Minuten. Protokollieren Sie den Fehler, wenn das Zeitlimit überschritten wird. Dies weist Sie zumindest in die Richtung einer der mit dem Problem befassten Sperren. Abhängig von der Variabilität des Timings kann es sein, dass Sie nach einigen Läufen Glück haben und beide Schlösser finden. Verwenden Sie Funktionsfehlercodes / -Bedingungen, um eine Pseudo-Stack-Ablaufverfolgung zu protokollieren, nachdem die zeitgesteuerte Wartezeit nicht ermittelt hat, wie Sie überhaupt dorthin gelangt sind. Dies sollte Ihnen helfen, den Thread zu identifizieren, der an dem Problem beteiligt ist.
Sie können auch versuchen, eine Wrapper-Bibliothek um Ihre Mutex- / Semaphor-Services zu erstellen. Verfolgen Sie, welche Threads über jeden Mutex verfügen und welche Threads auf den Mutex warten. Erstellen Sie einen Monitor-Thread, der überprüft, wie lange Threads blockiert wurden. Triggern Sie nach einer angemessenen Zeit und geben Sie die Statusinformationen aus, die Sie verfolgen.
Irgendwann wird eine einfache Überprüfung des alten Codes erforderlich sein.
quelle
Der erste Schritt (wie Péter sagt) ist die Protokollierung. Nach meiner Erfahrung ist dies jedoch oft problematisch. Bei starker Parallelverarbeitung ist dies oft nicht möglich. Ich musste etwas Ähnliches einmal mit einem neuronalen Netzwerk debuggen, das 100k Knoten pro Sekunde verarbeitete. Der Fehler trat erst nach mehreren Stunden auf und selbst eine einzige Ausgabezeile verlangsamte die Dinge so sehr, dass es Tage gedauert hätte. Wenn eine Protokollierung möglich ist, konzentrieren Sie sich weniger auf die Daten, sondern mehr auf den Programmfluss, bis Sie wissen, in welchem Teil dies geschieht. Nur eine einfache Zeile am Anfang jeder Funktion und wenn Sie die richtige Funktion finden, teilen Sie diese in kleinere Abschnitte auf.
Eine andere Möglichkeit besteht darin, Teile des Codes und der Daten zu entfernen, um den Fehler zu lokalisieren. Vielleicht schreiben Sie sogar ein kleines Programm, das nur einige der Klassen benötigt und nur die grundlegendsten Tests ausführt (natürlich immer noch in mehreren Threads). Entfernen Sie alle GUI-bezogenen Elemente, z. B. alle Ausgaben zum aktuellen Verarbeitungsstatus. (Ich fand die Benutzeroberfläche oft genug die Quelle des Fehlers)
Versuchen Sie in Ihrem Code, den gesamten logischen Kontrollfluss zwischen dem Initialisieren und dem Aufheben der Sperre zu befolgen. Ein häufiger Fehler könnte darin bestehen, am Anfang einer Funktion zu sperren und am Ende zu entsperren, aber irgendwo dazwischen eine bedingte return-Anweisung zu haben. Ausnahmen könnten ebenfalls die Freigabe verhindern.
quelle
Meine besten Freunde waren Print- / Log-Statements an interessanten Stellen im Code. Diese helfen mir normalerweise dabei, besser zu verstehen, was wirklich in der App vor sich geht, ohne das Timing zwischen verschiedenen Threads zu stören, was die Reproduktion des Fehlers verhindern könnte.
Wenn das fehlschlägt, ist meine einzige verbleibende Methode, auf den Code zu starren und zu versuchen, ein mentales Modell der verschiedenen Threads und Interaktionen aufzubauen und über mögliche verrückte Wege nachzudenken, um das zu erreichen, was anscheinend passiert ist :-) Aber ich nicht betrachte mich als einen sehr erfahrenen Deadlock-Slayer. Hoffentlich können andere bessere Ideen geben, von denen ich auch lernen kann :-)
quelle
Versuchen Sie zunächst, den Autor des Codes zu ermitteln. Er wird wahrscheinlich die Idee haben, was er geschrieben hat. Selbst wenn Sie beide das Problem nicht einfach durch ein Gespräch herausfinden können, können Sie sich zumindest mit ihm zusammensetzen, um den Deadlock-Teil herauszufinden, der viel schneller ist, als wenn Sie seinen Code ohne Hilfe verstehen.
Gelingt dies nicht, wie Péter Török sagte, ist Logging wahrscheinlich der richtige Weg. Meines Wissens hat Debugger in einer Multithreading-Umgebung schlechte Arbeit geleistet. Versuchen Sie herauszufinden, wo sich die Sperre befindet, und ermitteln Sie, auf welche Ressourcen Sie warten und in welchem Zustand die Rennbedingungen vorliegen.
quelle
Diese Frage zieht mich an;) Betrachten Sie sich als erstes als glücklich, da Sie das Problem bei jedem Durchgang konsistent reproduzieren konnten. Wenn Sie jedes Mal dieselbe Ausnahme mit demselben Stacktrace erhalten, sollte dies ziemlich einfach sein. Wenn nicht, vertrauen Sie dem Stacktrace nicht so sehr, sondern überwachen Sie lediglich den Zugriff auf die globalen Objekte und deren Statusänderungen während der Ausführung.
quelle
Wenn Sie Deadlocks debuggen müssen, sind Sie bereits in Schwierigkeiten. Verwenden Sie Schlösser in der Regel so schnell wie möglich - oder wenn möglich überhaupt nicht. Jegliche Situation, in der Sie eine Sperre aufheben und dann zu nicht-trivialem Code wechseln, sollte vermieden werden.
Das hängt natürlich von Ihrer Programmierumgebung ab, aber Sie sollten sich Dinge wie sequentielle Warteschlangen ansehen, mit denen Sie möglicherweise nur von einem einzigen Thread aus auf eine Ressource zugreifen können.
Und dann gibt es eine alte, aber unfehlbare Strategie: Weisen Sie jeder Sperre eine "Stufe" zu, beginnend mit Stufe 0. Wenn Sie eine Sperre der Stufe 0 nehmen, dürfen Sie keine anderen Sperren verwenden. Nachdem Sie eine Sperre der Stufe 1 erhalten haben, können Sie eine Sperre der Stufe 0 erhalten. Nachdem du ein Level 10 Schloss genommen hast, kannst du Schlösser mit Level 9 oder niedriger nehmen.
Wenn dies nicht möglich ist, müssen Sie den Code korrigieren, da Sie auf Deadlocks stoßen.
quelle