it-swarm-eu.dev

Wie teste ich Multithread-Code?

Gibt es Möglichkeiten, Ihren Multithread-Code auf Rennbedingungen und Deadlocks zu testen?

Um zu sehen, ob sie so funktionieren, wie sie sein sollten ...

34
Tamara Wijsman

SCHACH , ein Projekt von Microsoft Research. Zitieren ihrer Website:

CHESS ist ein Tool zum Auffinden und Reproduzieren von Heisenbugs in gleichzeitigen Programmen. CHESS führt wiederholt einen gleichzeitigen Test durch, um sicherzustellen, dass jeder Lauf eine andere Verschachtelung aufweist. Wenn eine Verschachtelung zu einem Fehler führt, kann CHESS die Verschachtelung für ein verbessertes Debugging reproduzieren. CHESS ist sowohl für verwaltete als auch für native Programme verfügbar.

Update (23.09.2015): Für C, C++ und Go können Sie ThreadSanitizer verwenden.

19
Josh Kelley

Valgrind hat Helgrind was wirklich hilft. Dies hilft nicht nur dabei, auf Rennen hinzuweisen, die zu Hunger oder Stillstand führen könnten, sondern die leichte Verlangsamung der Profilierung des Programms macht manchmal Rennen sichtbar, die sonst möglicherweise nicht zu sehen sind.

Also, selbst wenn Sie mit einer Art sperrenfreier Methode Kommando spielen, hilft es trotzdem :)

Es ist jedoch POSIX-zentriert. Es wird mit Headern geliefert, die einfache Unit-Test-Bibliotheken wie TAP auf einfache Weise darauf aufmerksam machen, dass es ausgeführt wird, was ebenfalls sehr hilfreich ist. Zum Beispiel könnten Sie einen Thread haben, der normalerweise nicht blockiert, wenn Sie versuchen, eine Sperre zu erlangen. Gehen Sie vor und blockieren Sie (möglicherweise zufällig), nur um den Hunger zu simulieren.

10
Tim Post

Ich erinnere mich nicht genau an die Details, aber das ist die allgemeine Idee. Ich habe es nur einmal gemacht, aber ich habe den wiedereintretenden Code vom Code getrennt, der die Aufgabe ausführt, und dabei eine Schnittstelle verwendet, um die Aufgabenklasse verspotten zu können.

Dann habe ich mein Modell so gestaltet, dass es einen Anruf sperren kann, sodass ich weiß, dass sich der Thread im kritischen Bereich befindet. Rufen Sie ihn dann erneut auf und überprüfen Sie, ob er wartet, bevor Sie den ersten Thread freigeben und sauber beenden.

So ähnlich.

Ich bin mir nicht sicher, ob dies für komplexere Szenarien funktionieren würde, aber es hilft, das Verhalten bei Refactorings beizubehalten.

Bei JAOO/GOTO in diesem Jahr habe ich diese Präsentation gesehen:

http://gotocon.com/aarhus-2010/presentation/Testing%20Asynchronous%20Behaviour%20in%20an%20Instant%20Messaging%20Server

Der Trick besteht darin, dass Sie modellieren, was Ihre Hairball-Anwendung tun soll, und zwar in Bezug auf Aufrufschritte sowie die tatsächlichen Vorgänge in Ihrer Anwendung. Die John Hughes-Software versucht dann systematisch viele Permutationen von Aufrufschritten wiederholt in parallel und prüft anschließend, ob der Status der Anwendung mit dem Status des Modells übereinstimmt. Wenn ein Fehler gefunden wird, kann die Software die Schritte auf den minimalen Fall reduzieren, der den Fehler verursacht.

Er demonstrierte live, wie man mehrere Fehler in Kernbibliotheken von Erlang entdeckt, die seit 15 Jahren lauern und gelegentlich gemeldet wurden, aber niemand konnte herausfinden, woher sie kamen und wie man sie behebt. Mit den minimalen Fällen, die von der Software gemeldet wurden, konnte der Bibliotheksbetreuer jeden Fehler innerhalb eines Tages beheben.

Es war SO beeindruckend.

John Hughes verkauft diese Software über seine Firma.

2
user1249
  1. Tests mit nicht reproduzierbaren Ergebnissen sind nutzlos. Das schließt völlig zufällige Tests aus, lässt aber Tests zu, die aus pseudozufälligen Sequenzen generiert wurden.
  2. Jeder Akteur in einer gleichzeitigen Umgebung verfügt über algorithmische oder anderweitig nicht parallele Komponenten, die mit herkömmlichen Mitteln getestet werden können. Nach dem Testen müssen alle verbleibenden Fehler in der Parallelitätslogik liegen.
  3. Die Ereignisse in einem gleichzeitigen System sind immer eine lineare Folge von Ereignissen. Wenn zur Messung der Zeit genügend Präzision verwendet wird, treten "gleichzeitig" keine Ereignisse auf. Das bedeutet, dass die Akteure in einem gleichzeitigen System getestet werden können, indem Ereignisse nacheinander generiert werden. Das Erfassen der Ereignissequenz zum Zeitpunkt des Ausfalls eines gleichzeitigen Systems liefert die erforderlichen Testfälle.
  4. Der Code, der den Akteuren Lebendigkeit (Threads) verleiht, wird häufig vom Betriebssystem oder von Systembibliotheken bereitgestellt. Es ist davon auszugehen, dass der Code nicht getestet werden muss. Der für die Kommunikation und Synchronisation zuständige Code wird normalerweise vom Anwendungsprogrammierer geschrieben. Dieser Code kann getestet werden, ohne den Systemcode aufzurufen, dh ohne Threads zu starten.
  5. Randbedingungen im algorithmischen Code (Warteschlange leer) erfordern häufig die Behandlung im Synchronisationscode, und dies ist ein gutes Ziel für Tests.
  6. Das Definieren von Proxys um den Systemcode (t.wait ()) ermöglicht die Verwendung von Stubs/Mocks der Funktionalität während des Testens.
2
Apalala

Sie können meinen Relacy Race Detector ausprobieren. Es wurde entwickelt, um Synchronisationsalgorithmen wie Producer-Consumer-Warteschlangen und gleichzeitige Container sorgfältig und präzise zu überprüfen, ist jedoch nicht sehr gut für die Überprüfung ganzer Programme geeignet. Vielleicht ist es jedoch eine gute Idee, Synchronisation und Mutexe trotzdem über ein Programm zu verteilen, sondern die Synchronisation auf spezielle Komponenten zu konzentrieren (die mit Relacy überprüft werden können).

1
Dmitry Vyukov

Es ist nicht einfach, aber im Grunde ist die einzige Möglichkeit, den Multithread-Code gleichzeitig von mehreren Threads aus aufzurufen nd das Timing und die Reihenfolge zufällig zu ändern durch Spielen mit zufälligen Thread.sleep() und Thread.yield() Aufrufe (unter der Annahme von Java).

Es gibt auch fertige Tools (wie TestNG), die etwas wie oben beschrieben tun, aber meines Wissens noch nicht sehr ausgereift sind.

0
Joonas Pulakka

Kein strenger Komponententest, sondern eine Laufzeitprüfung, die mir bei einigen zeitweise fehlgeschlagenen Tests geholfen hat. Es ist schnell und schmutzig, aber es hat funktioniert.

Wenn ein Mutex gewährt wird, verfolge ich, welcher Thread ihn hat. Alle Mutex-Anfragen haben eine zweiunddreißigste Zeitüberschreitung, nach der sie einen Deadlock schreien.

Ich kann dann die Liste der gewährten Mutexe verwenden, um zu sehen, welcher Thread den blockierenden Mutex enthält und warum so lange. In meinen bisherigen Fällen liegt es daran, dass es an etwas anderem festgefahren war, sodass ich diese Situation dann beheben kann.

Dies hat bei mir funktioniert, da meine Mutexe über eine plattformübergreifende Wrapper-Klasse verfügen, die das Einfügen von Aufzeichnungen und Zeitüberschreitungen erleichtert. Ich wusste auch genug über die Anwendung, um zu wissen, dass sie niemals 30 Sekunden lang auf einem Mutex blockiert werden sollte.

Es ist vielleicht kein ganz allgemeiner Zweck, aber es spart viel Debugging für etwa ein paar Stunden Programmieraufwand. Der Overhead ist vernachlässigbar und kann nur per Debug erstellt werden.

Ich werde versuchen, es zu erweitern, um verschachtelte Mutex-Anforderungssequenzen aufzuzeichnen, und zu prüfen, ob irgendwelche potenziell Deadlock-induzierend sind (z. B. ein Thread sperrt A, dann B und ein anderer sperrt B, dann A), anstatt nur tatsächlich Deadlock-induzierend, aber bisher Es war ein großer Vorteil für triviale Anstrengungen.

0
Andy Krouwel