Welche Lehren haben Sie aus einem Projekt gezogen, das aufgrund von schlechtem Multithreading fast / tatsächlich gescheitert ist?
Manchmal schreibt das Framework ein bestimmtes Threading-Modell vor, das es schwieriger macht, Dinge um eine Größenordnung richtig zu machen.
Ich habe mich noch nicht von dem letzten Fehler erholt und bin der Meinung, dass es für mich besser ist, an nichts zu arbeiten, was mit Multithreading in diesem Rahmen zu tun hat.
Ich fand, dass ich gut in Multithreading-Problemen war, die eine einfache Verzweigung / Verbindung haben und bei denen Daten nur in eine Richtung übertragen werden (während Signale in kreisförmiger Richtung übertragen werden können).
Ich kann keine GUI verarbeiten, in der einige Arbeiten nur an einem streng serialisierten Thread (dem "Hauptthread") und andere Arbeiten nur an einem anderen Thread als dem Hauptthread (den "Arbeitsthreads") ausgeführt werden können Dabei müssen Daten und Nachrichten zwischen N Komponenten in alle Richtungen übertragen werden (ein vollständig verbundener Graph).
Zu der Zeit, als ich dieses Projekt für ein anderes verließ, gab es überall Deadlock-Probleme. Ich habe gehört, dass es 2-3 Monate später mehreren anderen Entwicklern gelungen ist, alle Deadlock-Probleme zu beheben, bis sie an Kunden versendet werden können. Ich habe es nie geschafft herauszufinden, dass mir das fehlende Wissen fehlt.
Etwas über das Projekt: Die Anzahl der Nachrichten-IDs (ganzzahlige Werte, die die Bedeutung eines Ereignisses beschreiben, das unabhängig vom Threading in die Nachrichtenwarteschlange eines anderen Objekts gesendet werden kann) beträgt mehrere Tausend. Eindeutige Zeichenfolgen (Benutzernachrichten) sind ebenfalls ungefähr tausend.
Hinzugefügt
Die beste Analogie, die ich von einem anderen Team erhalten habe (unabhängig von meinen früheren oder gegenwärtigen Projekten), war, "die Daten in eine Datenbank zu stellen". ("Datenbank" bezieht sich auf Zentralisierung und atomare Aktualisierungen.) In einer GUI, die in mehrere Ansichten fragmentiert ist, die alle auf demselben "Hauptthread" ausgeführt werden, und das gesamte Nicht-GUI-Schwergewicht in einzelnen Arbeitsthreads ausgeführt wird, sollten die Daten der Anwendung verwendet werden in einer einzigen Plase gespeichert werden, die sich wie eine Datenbank verhält, und die "Datenbank" alle "atomaren Aktualisierungen" mit nicht trivialen Datenabhängigkeiten verarbeiten lassen. Alle anderen Teile der GUI behandeln nur das Zeichnen von Bildschirmen und sonst nichts. Die UI-Teile könnten Dinge zwischenspeichern und der Benutzer wird nicht bemerken, ob sie im Bruchteil einer Sekunde veraltet sind, wenn sie richtig entworfen wurden. Diese "Datenbank" wird auch als "Dokument" bezeichnet. in der Document-View-Architektur. Leider - nein, meine App speichert tatsächlich alle Daten in den Ansichten. Ich weiß nicht, warum es so war.
Mitwirkende:
(Mitwirkende müssen keine realen / persönlichen Beispiele verwenden. Lehren aus anekdotischen Beispielen sind ebenfalls willkommen, wenn sie von Ihnen als glaubwürdig beurteilt werden.)
Antworten:
Meine Lieblingsstunde - sehr hart gewonnen! - Ist das in einem Multithread-Programm der Scheduler ein hinterhältiges Schwein, das Sie hasst? Wenn etwas schief gehen kann, werden sie es tun, aber auf unerwartete Weise. Wenn Sie etwas falsch machen, werden Sie seltsamen Heisenbugs nachjagen (weil jede Instrumentierung, die Sie hinzufügen, die Timings ändert und Ihnen ein anderes Laufmuster gibt).
Die einzig vernünftige Möglichkeit, dies zu beheben, besteht darin, die gesamte Thread-Handhabung streng in einen so kleinen Code umzuwandeln, der alles in Ordnung bringt und sehr konservativ ist, um sicherzustellen, dass die Sperren ordnungsgemäß gehalten werden (und dies auch bei einer global konstanten Reihenfolge der Erfassung). . Der einfachste Weg, dies zu tun, besteht darin, den Speicher (oder andere Ressourcen) nicht zwischen Threads zu teilen, außer für Nachrichten, die asynchron sein müssen. Auf diese Weise können Sie alles andere in einem Stil schreiben, der keine Threads enthält. (Bonus: Das Skalieren auf mehrere Computer in einem Cluster ist viel einfacher.)
quelle
is that in a multithreaded program the scheduler is a sneaky swine that hates you.
- Nein, tut es nicht, es macht genau das, was du ihm gesagt hast :)Hier sind einige grundlegende Lektionen, die ich mir gerade vorstellen kann (nicht aus fehlgeschlagenen Projekten, sondern aus realen Problemen, die bei realen Projekten auftreten):
quelle
Wir haben einen Teil geerbt, in dem das GUI-Projekt ein Dutzend Threads verwendet. Es gibt nichts als Probleme. Deadlocks, Rennprobleme, Cross-Thread-GUI-Aufrufe ...
quelle
Java 5 und höher verfügt über Executoren, die das Handling von Programmen im Fork-Join-Stil mit mehreren Threads vereinfachen sollen.
Verwenden Sie diese, es wird viel von dem Schmerz entfernen.
(und ja, das habe ich aus einem Projekt gelernt :))
quelle
Ich habe einen Hintergrund in eingebetteten Echtzeitsystemen. Sie können nicht testen, ob keine Probleme durch Multithreading auftreten. (Sie können manchmal die Anwesenheit bestätigen). Der Code muss nachweislich korrekt sein. Best Practice für alle Thread-Interaktionen.
quelle
Eine Analogie aus einem Multithreading-Kurs, den ich letztes Jahr besucht habe, war sehr hilfreich. Die Thread-Synchronisation ist wie ein Verkehrssignal, das eine Kreuzung (Daten) davor schützt, von zwei Autos (Threads) gleichzeitig verwendet zu werden. Der Fehler, den viele Entwickler machen, besteht darin, die Lichter in den meisten Teilen der Stadt rot zu machen, um ein Auto durchzulassen, weil sie der Meinung sind, dass es zu schwierig oder gefährlich ist, das genaue Signal herauszufinden, das sie benötigen. Dies funktioniert möglicherweise gut, wenn der Datenverkehr gering ist, führt jedoch zu einem Stillstand, wenn Ihre Anwendung wächst.
Das wusste ich theoretisch bereits, aber nach diesem Kurs blieb mir die Analogie wirklich erhalten, und ich war erstaunt, wie oft ich danach ein Threading-Problem untersuchte und eine riesige Warteschlange fand oder Interrupts während eines Schreibvorgangs in eine Variable überall deaktiviert wurden Es wurden nur zwei Threads verwendet oder Mutexe wurden lange gehalten, wenn sie überarbeitet werden konnten, um sie insgesamt zu vermeiden.
Mit anderen Worten, einige der schlimmsten Threading-Probleme werden durch Overkill verursacht, der versucht, Threading-Probleme zu vermeiden.
quelle
Versuchen Sie es erneut.
Zumindest für mich war das Üben ein Unterschied. Nachdem Sie einige Male Multithread- und verteilte Arbeiten ausgeführt haben, haben Sie einfach den Dreh raus.
Ich denke, das Debuggen macht es wirklich schwierig. Ich kann Multithread-Code mit VS debuggen, aber ich bin wirklich ratlos, wenn ich gdb verwenden muss. Wahrscheinlich meine Schuld.
Eine andere Sache, über die Sie mehr lernen, sind sperrfreie Datenstrukturen.
Ich denke, diese Frage kann wirklich verbessert werden, wenn Sie das Framework angeben. Beispielsweise unterscheiden sich .NET-Thread-Pools und Hintergrund-Worker erheblich von QThread. Es gibt immer ein paar plattformspezifische Fallstricke.
quelle
Ich habe gelernt, dass Rückrufe von Modulen niedrigerer Ebene zu Modulen höherer Ebene ein großes Übel sind, weil sie dazu führen, dass Sperren in umgekehrter Reihenfolge erworben werden.
quelle