Sollte ich Unit-Test auf Multithreading-Probleme vor dem Schreiben einer Sperre testen?

8

Ich schreibe eine Klasse, von der ich weiß, dass sie eine Sperre benötigt, weil die Klasse Thread-sicher sein muss. Aber da ich Test-Driven-Developing bin, weiß ich, dass ich vor dem Erstellen eines Tests keine Codezeile schreiben kann. Und das finde ich sehr schwierig, da die Tests am Ende sehr komplex werden. Was machst du normalerweise in diesen Fällen? Gibt es ein Tool, um damit zu helfen?

Diese Frage ist .NET spezifisch

Jemand hat nach dem Code gefragt:

%Vor%     
Jader Dias 26.04.2009, 21:58
quelle

10 Antworten

3

Wenn Sie Multi-Threaded-Code schreiben, müssen Sie Ihr Gehirn noch mehr nutzen als gewöhnlich. Sie müssen logisch über jede einzelne Codezeile nachdenken, unabhängig davon, ob sie threadsicher ist oder nicht. Es ist, als würde man die Richtigkeit einer mathematischen Formel beweisen - man kann Dinge wie "N + 1 & gt; N für alle N" nicht beweisen, indem man nur Beispiele für Werte von N gibt, mit denen die Formel wahr ist. Ebenso ist es nicht möglich, zu beweisen, dass eine Klasse threadsicher ist, indem Testfälle geschrieben werden, die versuchen, Probleme damit aufzudecken. Mit einem Test ist es nur möglich zu beweisen, dass ein Fehler vorliegt, aber nicht dass es keine Fehler gibt.

Das Beste, was Sie tun können, ist, den Bedarf an Multithread-Code zu minimieren. Vorzugsweise sollte die Anwendung keinen Multi-Threaded-Code haben (zum Beispiel indem sie sich auf Thread-sichere Bibliotheken und geeignete Entwurfsmuster stützt), oder sie sollte auf einen sehr kleinen Bereich beschränkt sein. Ihre StackQueue -Klasse sieht einfach genug aus, so dass Sie mit ein wenig Nachdenken sicher Thread-sicher machen können.

Angenommen, die Implementierungen Stack und Queue sind Thread-sicher (ich kenne die Bibliotheken von .NET nicht), müssen Sie Next() thread-safe machen. Count ist bereits threadsicher, da kein Client den von ihm zurückgegebenen Wert ohne das clientbasierte Sperren verwenden kann - Statusabhängigkeiten zwischen den Methoden würde sonst den Code brechen.

Next() ist nicht Thread-sicher, da es zwischen den Methoden Statusabhängigkeiten gibt. Wenn die Threads T1 und T2 stack.Count gleichzeitig aufrufen und 1 zurückgibt, erhält einer von ihnen den Wert mit stack.Pop() , der andere ruft stack.Pop() auf, wenn der Stack leer ist (was dann zu werfen scheint %Code%). Sie benötigen einen Stack und eine Warteschlange mit nicht blockierenden Versionen von InvalidOperationException und Pop() (solche, die im leeren Zustand null zurückgeben). Dann wäre der Code Thread-sicher, wenn er folgendermaßen geschrieben wird:

%Vor%     
Esko Luontola 26.04.2009, 23:26
quelle
6

Nun können Sie normalerweise Dinge wie ManualResetEvent verwenden, um einige Threads in einen erwarteten Problemzustand zu bringen, bevor Sie das Gate freigeben ... aber das deckt nur eine kleine Teilmenge von Threading-Problemen ab.

Für das größere Problem des Threading-Bugs gibt es CHESS (in Bearbeitung) - vielleicht eine Option in der Zukunft.

    
Marc Gravell 26.04.2009 22:08
quelle
4

Sie sollten in einem Komponententest nicht wirklich auf Fadensicherheit prüfen. Sie sollten wahrscheinlich einen separaten Satz von Stresstests für die Fadensicherheit haben.

Okay, nachdem Sie den Code veröffentlicht haben:

%Vor%

Dies ist ein großartiges Beispiel dafür, wo Sie Schwierigkeiten haben werden, einen Komponententest zu schreiben, der Threading-Probleme in Ihrem Code aufdeckt. Dieser Code benötigt möglicherweise eine Synchronisierung, da sich die Werte von this.queue.Count und this.stack.Count in der Mitte der Berechnung des Gesamtwerts ändern können, sodass ein Wert zurückgegeben werden kann, der nicht "korrekt" ist.

JEDOCH: Angesichts des Rests der Klassendefinition hängt nichts wirklich davon ab, dass Count ein konsistentes Ergebnis liefert, also ist es wirklich wichtig, ob es "falsch" ist? Es gibt keine Möglichkeit, das zu wissen, ohne zu wissen, wie andere Klassen in Ihrem Programm dieses verwenden. Dadurch wird das Testen von Threading-Problemen eher zu einem Integrationstest als zu einem Komponententest.

    
Mark Bessey 26.04.2009 22:12
quelle
2

TDD ist ein Werkzeug - und ein gutes - aber manchmal haben Sie Probleme, die mit einem bestimmten Werkzeug nicht gut gelöst werden. Ich würde vorschlagen, dass, wenn die Entwicklung der Tests übermäßig komplex ist, Sie TDD verwenden sollten, um die erwartete Funktionalität zu entwickeln, aber sich vielleicht auf Codeüberprüfung verlassen, um sich zu versichern, dass der von Ihnen hinzugefügte Sperrcode geeignet ist und Ihre Klasse Thread zulässt -safe.

Eine mögliche Lösung wäre, den Code zu entwickeln, der in das Schloss passt und es in seine eigene Methode bringt. Dann können Sie diese Methode fälschen, um Ihren Sperrcode zu testen. In Ihrem gefälschten Code können Sie einfach eine Wartezeit einrichten, um sicherzustellen, dass der zweite Thread, der auf den Code zugreift, auf die Sperre gewartet haben muss, bis der erste Thread abgeschlossen ist. Ohne genau zu wissen, was dein Code tut, kann ich nicht spezifischer sein als das.

    
tvanfosson 26.04.2009 22:15
quelle
2

Multithreading kann zu so komplexen Problemen führen, dass es fast unmöglich ist, Komponententests für sie zu schreiben. Sie könnten es schaffen, einen Komponententest zu schreiben, der eine 100% ige Fehlerrate hat, wenn er auf einem Code ausgeführt wird, aber nachdem Sie ihn bestanden haben, ist es ziemlich wahrscheinlich, dass es noch Race Conditions und ähnliche Probleme im Code gibt.

Das Problem mit Threading-Problemen ist, dass sie zufällig auftauchen. Selbst wenn ein Komponententest bestanden wird, bedeutet dies nicht unbedingt, dass der Code funktioniert. Also in diesem Fall gibt TDD ein falsches Gefühl der Sicherheit und könnte sogar als eine schlechte Sache betrachtet werden.

Und es ist auch daran zu erinnern, dass wenn eine Klasse threadsicher ist, Sie sie aus mehreren Threads ohne Probleme verwenden können - aber wenn eine Klasse nicht threadsicher ist, bedeutet das nicht sofort, dass Sie sie nicht aus mehreren Threads verwenden können ohne Probleme. Es könnte in der Praxis immer noch threadsicher sein, aber niemand will nur die Verantwortung dafür übernehmen, dass es nicht threadsicher ist. Und wenn es in der Praxis threadsicher ist, ist es unmöglich, einen Komponententest zu schreiben, der wegen Multi-Threading nicht funktioniert. (Natürlich sind die meisten nicht threadsicheren Klassen wirklich nicht Thread-sicher und werden glücklich ausfallen.)

    
Mikko Rantanen 26.04.2009 22:15
quelle
1

Nur um klar zu sein, müssen nicht alle Klassen Schlösser threadsicher sein.

Wenn Ihre Tests zu komplex werden, kann das ein Symptom dafür sein, dass Ihre Klassen oder Methoden zu kompliziert sind, zu eng gekoppelt sind oder zu viel Verantwortung übernehmen. Versuchen Sie, dem Prinzip der einheitlichen Verantwortung zu folgen.

Würde es Ihnen etwas ausmachen, konkretere Informationen über Ihre Klasse zu veröffentlichen?

    
Robert Venables 26.04.2009 22:07
quelle
1

Besonders bei Mehrkernsystemen können Sie normalerweise Threading-Probleme testen, nur nicht deterministisch.

In der Regel mache ich mehrere Threads, die den fraglichen Code durchbohren und die Anzahl der unerwarteten Ergebnisse zählen. Diese Threads werden für eine kurze Zeit ausgeführt (in der Regel 2-3 Sekunden). Verwenden Sie dann Interlocked.CompareExchange, um ihre Ergebnisse hinzuzufügen und normal zu beenden. Mein Test, der sie hochgefahren hat, ruft dann .Join auf und überprüft dann, ob die Anzahl der Fehler 0 war.

Sicher, es ist nicht idiotensicher, aber mit einer Multi-Core-CPU macht es normalerweise die Arbeit gut genug, um meinen Kollegen zu zeigen, dass es ein Problem gibt, das mit dem Objekt angesprochen werden muss (vorausgesetzt, Gewindezugang).

    
Jonathan Rupp 26.04.2009 22:47
quelle
1

Ich stimme den anderen Postern zu, dass Multithreading-Code vermieden werden sollte oder zumindest auf kleine Teile Ihrer Anwendung beschränkt sein sollte. Allerdings wollte ich immer noch einen Weg, diese kleinen Portionen zu testen. Ich arbeite an einem .NET-Port der MultithreadedTC-Java-Bibliothek . Mein Port heißt Ticking-Test und der Quellcode wird auf Google veröffentlicht Code.

Mit MultithreadedTC können Sie eine Testklasse mit mehreren Methoden schreiben, die mit einem TestThread-Attribut markiert sind. Jeder Thread kann auf eine bestimmte Anzahl von Ticks warten, um anzukommen, oder er kann behaupten, was er für die aktuelle Tick-Anzahl halten soll. Wenn alle aktuellen Threads geblockt sind, erhöht ein Coordinator-Thread die Tick-Anzahl und weckt alle Threads auf, die auf die nächste Tick-Anzahl warten. Wenn Sie interessiert sind, sehen Sie sich die MultithreadedTC-Übersicht für Beispiele an. MultithreadedTC wurde von einigen der gleichen Leute geschrieben, die FindBugs geschrieben haben.

Ich habe meinen Port erfolgreich in einem kleinen Projekt verwendet. Das wichtigste fehlende Feature ist, dass ich keine neu erstellten Threads während des Tests verfolgen kann.

    
Don Kirkby 28.04.2009 19:43
quelle
0

Die meisten Komponententests werden nacheinander und nicht gleichzeitig ausgeführt, sodass keine Probleme mit Nebenläufigkeit auftreten.

Die Code-Überprüfung durch Programmierer mit Nebenläufigkeitsexpertise ist Ihre beste Wahl.

Diese bösen Nebenläufigkeitsprobleme treten oft erst dann auf, wenn Sie genug Produkt im Feld haben, um einige statistisch relevante Trends zu generieren. Sie sind manchmal unglaublich schwer zu finden, daher ist es oft am besten, den Code gar nicht erst schreiben zu müssen. Verwenden Sie nach Möglichkeit vorhandene threadsichere Bibliotheken und bewährte Entwurfsmuster.

    
DanM 27.04.2009 00:50
quelle
0
  • Sie finden Ihre "Invariante" - etwas, das unabhängig von der Anzahl und Verschachtelung von Client-Threads wahr bleiben muss. Das ist der schwierige Teil.
  • Dann schreibe einen Test, der eine Anzahl von Threads hervorbringt, übt die Methode aus und behauptet, dass die Invariante immer noch gilt. Dies wird fehlschlagen - weil es keinen Code gibt, um es threadsicher zu machen. Rot
  • Fügen Sie Code hinzu, um ihn threadsicher zu machen und den Stresstest zu bestehen. Grün. Refactor nach Bedarf.

Weitere Einzelheiten finden Sie im GOOS-Buch für die Kapitel zum Ende des Multi-Thread-Codes.

    
Gishu 01.06.2011 12:34
quelle