it-swarm.com.de

Wie man erklärt, warum Multithreading schwierig ist

Ich bin ein ziemlich guter Programmierer, mein Chef ist auch ein ziemlich guter Programmierer. Obwohl er einige Aufgaben wie Multithreading zu unterschätzen scheint und wie schwierig es sein kann (ich finde es sehr schwierig, mehr als ein paar Threads auszuführen, darauf zu warten, dass alle fertig sind, und dann Ergebnisse zurückzugeben).

In dem Moment, in dem Sie sich Sorgen um Deadlocks und Rennbedingungen machen müssen, fällt es mir sehr schwer, aber der Chef scheint das nicht zu schätzen - ich glaube nicht, dass er jemals darauf gestoßen ist. Einfach ein Schloss drauf schlagen ist so ziemlich die Einstellung.

Wie kann ich ihn vorstellen oder erklären, warum er die Komplexität von Parallelität, Parallelität und Multithreading möglicherweise unterschätzt? Oder vielleicht irre ich mich?

Bearbeiten: Nur ein bisschen über das, was er getan hat - durchlaufen Sie eine Liste, und erstellen Sie für jedes Element in dieser Liste einen Thread, der einen Datenbankaktualisierungsbefehl basierend auf den Informationen in diesem Element ausführt. Ich bin nicht sicher, wie er kontrolliert hat, wie viele Threads gleichzeitig ausgeführt wurden. Ich denke, er muss sie einer Warteschlange hinzugefügt haben, wenn zu viele ausgeführt wurden (er hätte kein Semaphor verwendet).

86
Mr Shoubs
  1. Wenn Sie sich auf mathematische Erfahrungen verlassen können, veranschaulichen Sie, wie ein normaler Ausführungsfluss, der im Wesentlichen deterministisch ist, nicht nur mit mehreren Threads nicht deterministisch, sondern exponentiell komplex wird, da Sie jede mögliche Verschachtelung von Maschinenanweisungen sicherstellen müssen werde immer noch das Richtige tun. Ein einfaches Beispiel für ein verlorenes Update oder eine schmutzige Lesesituation ist oft ein Augenöffner.

  2. "Schlagen Sie ein Schloss drauf" ist die triviale Lösung ... es löst alle Ihre Probleme wenn Sie sind nicht über die Leistung besorgt. Versuchen Sie zu veranschaulichen, wie viel Leistung es bringen würde, wenn beispielsweise Amazon die gesamte Ostküste sperren müsste, wenn jemand in Atlanta ein Buch bestellt!

29
Kilian Foth

Multithreading ist einfach. Das Codieren einer Anwendung für Multithreading ist sehr, sehr einfach.

Es gibt einen einfachen Trick: Verwenden Sie eine gut gestaltete Nachrichtenwarteschlange (do not roll your own), um Daten zwischen Threads zu übertragen.

Der schwierige Teil besteht darin, dass mehrere Threads ein freigegebenes Objekt auf magische Weise aktualisieren. Dann wird es fehleranfällig, weil die Leute nicht auf die vorhandenen Rennbedingungen achten.

Viele Leute verwenden keine Nachrichtenwarteschlangen und versuchen, freigegebene Objekte zu aktualisieren und Probleme für sich selbst zu erstellen.

Was schwierig wird, ist das Entwerfen eines Algorithmus, der gut funktioniert, wenn Daten zwischen mehreren Warteschlangen übertragen werden. Das ist schwierig. Die Mechanik von gleichzeitig vorhandenen Threads (über gemeinsam genutzte Warteschlangen) ist jedoch einfach.

Beachten Sie außerdem, dass Threads share E/A-Ressourcen. Es ist unwahrscheinlich, dass ein E/A-gebundenes Programm (d. H. Netzwerkverbindungen, Dateivorgänge oder Datenbankoperationen) mit vielen Threads schneller abläuft.

Wenn Sie das Problem der Aktualisierung gemeinsam genutzter Objekte veranschaulichen möchten, ist dies ganz einfach. Setzen Sie sich mit einem Bündel Papierkarten über den Tisch. Schreiben Sie eine einfache Reihe von Berechnungen auf - 4 oder 6 einfache Formeln - mit viel Platz auf der Seite.

Hier ist das Spiel. Sie lesen jeweils eine Formel, schreiben eine Antwort und legen eine Karte mit der Antwort ab.

Jeder von euch wird die halbe Arbeit machen, oder? Du bist in der Hälfte der Zeit fertig, oder?

Wenn Ihr Chef nicht viel nachdenkt und gerade erst anfängt, kommt es zu Konflikten, und beide schreiben Antworten auf dieselbe Formel. Das hat nicht funktioniert, weil es eine inhärente Rassenbedingung zwischen euch beiden gibt, die vor dem Schreiben lesen. Nichts hindert Sie daran, die gleiche Formel zu lesen und die Antworten des anderen zu überschreiben.

Es gibt viele, viele Möglichkeiten, Rennbedingungen mit schlecht oder nicht gesperrten Ressourcen zu schaffen.

Wenn Sie alle Konflikte vermeiden möchten, schneiden Sie das Papier in einen Stapel Formeln. Sie nehmen eine aus der Warteschlange, schreiben die Antwort auf und veröffentlichen die Antworten. Keine Konflikte, da Sie beide aus einer Nachrichtenwarteschlange nur für einen Leser lesen.

79
S.Lott

Multithread-Programmierung ist wahrscheinlich die schwierigste Lösung für die Parallelität. Es ist im Grunde eine ziemlich niedrige Abstraktion dessen, was die Maschine tatsächlich tut.

Es gibt eine Reihe von Ansätzen, wie das Akteurmodell oder (Software) Transaktionsspeicher , die viel einfacher sind. Oder mit unveränderlichen Datenstrukturen (wie Listen und Bäumen) arbeiten.

Im Allgemeinen erleichtert eine ordnungsgemäße Trennung von Bedenken das Multithreading. Etwas, das allzu oft vergessen wird, wenn Leute 20 Threads erzeugen, die alle versuchen, denselben Puffer zu verarbeiten. Verwenden Sie Reaktoren, in denen Sie eine Synchronisierung benötigen, und übergeben Sie im Allgemeinen Daten zwischen verschiedenen Mitarbeitern mit Nachrichtenwarteschlangen.
Wenn Sie eine Sperre in Ihrer Anwendungslogik haben, haben Sie etwas falsch gemacht.

Also ja, technisch gesehen ist Multithreading schwierig.
"Slap a Lock on it" ist so ziemlich die am wenigsten skalierbare Lösung für Parallelitätsprobleme und macht den gesamten Zweck des Multithreading zunichte. Es wird ein Problem auf ein nicht gleichzeitiges Ausführungsmodell zurückgesetzt. Je mehr Sie es tun, desto wahrscheinlicher ist es, dass jeweils nur ein Thread ausgeführt wird (oder 0 in einem Deadlock). Es besiegt den ganzen Zweck.
Das ist wie zu sagen: "Die Probleme der 3. Welt zu lösen ist einfach. Wirf einfach eine Bombe darauf." Nur weil es eine triviale Lösung gibt, wird das Problem dadurch nicht trivial, da Sie auf die Qualität des Ergebnisses achten.

In der Praxis ist die Lösung dieser Probleme jedoch genauso schwierig wie bei allen anderen Programmierproblemen und wird am besten mit geeigneten Abstraktionen durchgeführt. Das macht es in der Tat ganz einfach.

25
back2dos

Ich denke, diese Frage hat einen nicht technischen Aspekt - IMO ist es eine Frage des Vertrauens. Wir werden häufig gebeten, komplexe Apps wie - oh, ich weiß nicht - Facebook zu reproduzieren. Ich bin zu dem Schluss gekommen, dass, wenn Sie dem Uneingeweihten/Management die Komplexität einer Aufgabe erklären müssen, in Dänemark etwas faul ist.

Selbst wenn andere Ninja-Programmierer die Aufgabe in 5 Minuten erledigen könnten, basieren Ihre Schätzungen auf Ihren persönlichen Fähigkeiten. Ihr Gesprächspartner sollte entweder lernen, Ihrer Meinung in dieser Angelegenheit zu vertrauen, oder jemanden einstellen, dessen Wort er zu akzeptieren bereit ist.

Die Herausforderung besteht nicht darin, die technischen Implikationen weiterzugeben, die die Menschen entweder ignorieren oder durch Gespräche nicht erfassen können, sondern eine Beziehung des gegenseitigen Respekts aufzubauen.

14
sunwukung

Ein einfaches Gedankenexperiment zum Verständnis von Deadlocks ist das Problem " Essensphilosoph ". Eines der Beispiele, mit denen ich beschreibe, wie schlecht die Rennbedingungen sein können, ist die Situation Therac 25 .

"Nur ein Schloss aufsetzen" ist die Mentalität von jemandem, der nicht auf schwierige Fehler mit Multithreading gestoßen ist. Und es ist möglich, dass er denkt, Sie übertreiben den Ernst der Situation (ich nicht - es ist möglich, Dinge in die Luft zu jagen oder Menschen zu töten Fehler im Rennzustand, insbesondere bei eingebetteter Software, die in Autos landet).

6
Tangurena

Kurze Antwort in zwei Worten: BEOBACHTBARER NONDETERMINISMUS

Lange Antwort: Es hängt davon ab, welchen Ansatz für die gleichzeitige Programmierung Sie aufgrund Ihres Problems verwenden. In dem Buch Konzepte, Techniken und Modelle der Computerprogrammierung erläutern die Autoren klar vier praktische Hauptansätze für das Schreiben gleichzeitiger Programme:

  • Sequentielle Programmierung : Ein Basisansatz ohne Parallelität;
  • Deklarative Parallelität : verwendbar, wenn kein beobachtbarer Nichtdeterminismus vorliegt;
  • Parallelität bei der Nachrichtenübermittlung : gleichzeitige Nachrichtenübermittlung zwischen vielen Entitäten, wobei jede Entität die Nachricht intern nacheinander verarbeitet;
  • Parallelität des gemeinsam genutzten Zustands : Thread, der gemeinsam genutzte passive Objekte mittels grobkörniger atomarer Aktionen aktualisiert, z. Schlösser, Monitore und Transaktionen;

Der einfachste dieser vier Ansätze ist neben der offensichtlichen sequentiellen Programmierung die deklarative Parallelität , da die mit diesem Ansatz geschriebenen Programme haben ) kein beobachtbarer Nichtdeterminismus . Mit anderen Worten, es gibt keine Rennbedingungen , da die Rennbedingung nur ein beobachtbares nichtdeterministisches Verhalten ist.

Das Fehlen eines beobachtbaren Nichtdeterminismus bedeutet jedoch, dass es einige Probleme gibt , die wir nicht mit deklarativer Parallelität angehen können. Hier kommen die letzten beiden nicht so einfachen Ansätze ins Spiel. Der nicht so einfache Teil ist eine Folge des beobachtbaren Nichtdeterminismus. Jetzt fallen beide unter ein Stateful Concurrent Model und sind auch in ihrer Ausdruckskraft gleichwertig. Aufgrund der ständig wachsenden Anzahl von Kernen pro CPU scheint die Branche in letzter Zeit mehr Interesse an der Parallelität der Nachrichtenübermittlung gezeigt zu haben, was sich im Anstieg der Bibliotheken für die Nachrichtenübermittlung zeigt (z. B. Akka für JVM). oder Programmiersprachen (zB Erlang ).

Die zuvor erwähnte Akka-Bibliothek, die von einem theoretischen Actor-Modell unterstützt wird, vereinfacht das Erstellen gleichzeitiger Anwendungen, da Sie sich nicht mehr mit Sperren, Monitoren oder Transaktionen befassen müssen. Andererseits erfordert es einen anderen Ansatz zum Entwerfen einer Lösung, d. H. Ein Denken darüber, wie Akteure hierarchisch zusammengesetzt werden können. Man könnte sagen, dass es eine völlig andere Denkweise erfordert, was am Ende wiederum noch schwieriger sein kann als die Verwendung der gemeinsamen Parallelität im Normalzustand.

Die gleichzeitige Programmierung ist aufgrund des beobachtbaren Nichtdeterminismus schwierig , aber wenn Sie den richtigen Ansatz für das gegebene Problem und die richtige Bibliothek verwenden, die diesen Ansatz unterstützt, dann viele Probleme können vermieden werden.

3
Jernej Jerin

Gleichzeitige Anwendungen sind nicht deterministisch. Mit der außergewöhnlich geringen Menge an Gesamtcode, die der Programmierer als anfällig erkannt hat, können Sie nicht steuern, wann ein Teil eines Threads/Prozesses in Bezug auf einen Teil eines anderen Threads ausgeführt wird. Das Testen ist schwieriger, dauert länger und es ist unwahrscheinlich, dass alle mit der Parallelität verbundenen Fehler gefunden werden. Wenn Defekte gefunden werden, die subtil sind, können sie nicht konsistent reproduziert werden, daher ist die Behebung schwierig.

Daher ist die einzig richtige gleichzeitige Anwendung nachweislich korrekt, was in der Softwareentwicklung nicht oft praktiziert wird. Infolgedessen ist die Antwort von S.Lot der beste allgemeine Rat, da die Weitergabe von Nachrichten relativ einfach als richtig zu beweisen ist.

3
mattnz

Mir wurde zuerst beigebracht, dass es Probleme aufwerfen kann, indem ich ein einfaches Programm sehe, das zwei Threads startet und beide gleichzeitig von 1 bis 100 auf der Konsole drucken lässt. Anstatt:

1
1
2
2
3
3
...

Sie bekommen so etwas mehr:

1
2
1
3
2
3
...

Führen Sie es erneut aus, und Sie erhalten möglicherweise völlig andere Ergebnisse.

Die meisten von uns wurden geschult, um davon auszugehen, dass unser Code nacheinander ausgeführt wird. Bei den meisten Multithreading-Vorgängen können wir dies nicht als "out of the box" betrachten.

0