Wie erreicht man Multithreading-Funktionalität in Hochsprachen wie Java mit nur einem Thread und einer Zustandsmaschine? Was ist zum Beispiel, wenn zwei Aktivitäten ausgeführt werden müssen (Berechnungen ausführen und E / A ausführen) und eine Aktivität blockieren kann?
Was Sie beschreiben, wird als kooperatives Multitasking bezeichnet , bei dem der CPU Aufgaben zugewiesen werden und diese nach einer selbstbestimmten Zeit oder Aktivität freiwillig freigegeben werden sollen. Eine Aufgabe, die nicht zusammenarbeitet, indem sie die CPU weiter nutzt oder das gesamte Werk blockiert und keinen Hardware-Watchdog-Timer hat, kann der Code, der die Aufgaben überwacht, nichts dagegen tun.
Was Sie in modernen Systemen sehen, wird als präemptives Multitasking bezeichnet. Dort müssen Aufgaben die CPU nicht freigeben, da der Supervisor dies für sie erledigt, wenn ein von der Hardware erzeugter Interrupt eintrifft. Die Interrupt-Serviceroutine im Supervisor speichert den Zustand der CPU und stellt ihn wieder her, wenn die Aufgabe das nächste Mal als zeitaufwändig eingestuft wird. Anschließend stellt sie den Zustand der nächsten auszuführenden Aufgabe wieder her und springt zurück, als wäre nichts geschehen . Diese Aktion wird als Kontextwechsel bezeichnet und kann teuer sein.
Ist die Verwendung von "state-machine only" eine sinnvolle Alternative zum Multithreading in Hochsprachen?
Lebensfähig? Sicher. Gesund? Manchmal. Ob Sie Threads oder eine Form von selbst gebrautem kooperativem Multitasking (z. B. Zustandsautomaten) verwenden, hängt von den Kompromissen ab, die Sie eingehen möchten.
Threads vereinfachen das Task-Design bis zu dem Punkt, an dem Sie jedes Programm als sein eigenes behandeln können, das zufällig den Datenraum mit anderen teilt. Dies gibt Ihnen die Freiheit, sich auf den jeweiligen Job zu konzentrieren, und nicht auf die gesamte Verwaltung und das gesamte Housekeeping, die erforderlich sind, damit der Job immer wieder ausgeführt wird. Da jedoch keine gute Tat ungestraft bleibt, zahlen Sie für all diese Bequemlichkeit bei Kontextwechseln. Viele Threads, die die CPU nach minimaler Arbeit auslasten (freiwillig oder durch blockierende Aktionen wie E / A), können beim Kontextwechsel viel Prozessorzeit in Anspruch nehmen. Dies gilt insbesondere dann, wenn Ihre Blockierungsvorgänge selten sehr lange blockieren.
Es gibt Situationen, in denen der kooperative Weg sinnvoller ist. Ich musste einmal eine Userland-Software für eine Hardware schreiben, die viele Datenkanäle über eine speicherabgebildete Schnittstelle übertrug, für die ein Abruf erforderlich war. Jeder Kanal war ein Objekt, das so aufgebaut war, dass ich es entweder als Thread ausführen oder wiederholt einen einzelnen Abfragezyklus ausführen konnte.
Die Leistung der Multithread-Version war aus genau dem Grund, den ich oben dargelegt habe, überhaupt nicht gut: Jeder Thread erledigte nur minimale Arbeit und gab dann die CPU frei, sodass die anderen Kanäle etwas Zeit haben konnten und viele Kontextwechsel verursachten. Das Freigeben der Threads bis zur Freigabe half beim Durchsatz, führte jedoch dazu, dass einige Kanäle nicht gewartet wurden, bevor die Hardware einen Pufferüberlauf erlebte, da sie nicht schnell genug eine Zeitscheibe erhielten.
Die Single-Threaded-Version, die sogar Iterationen jedes Kanals ausführte, lief wie ein verbrühter Affe, und die Last auf dem System fiel wie ein Stein. Die Strafe, die ich für die zusätzliche Leistung gezahlt habe, bestand darin, die Aufgaben selbst zu jonglieren. In diesem Fall war der Code, der dafür benötigt wurde, so einfach, dass die Kosten für die Entwicklung und Wartung die Leistungsverbesserung durchaus wert waren. Ich denke, das ist wirklich das Endergebnis. Wären meine Threads solche gewesen, die darauf gewartet hätten, dass ein Systemaufruf zurückkommt, hätte sich die Übung wahrscheinlich nicht gelohnt.
Das bringt mich zu Cox 'Kommentar: Threads sind nicht ausschließlich für Leute gedacht, die keine Zustandsautomaten schreiben können. Einige Leute sind durchaus in der Lage, dies zu tun, entscheiden sich jedoch für die Verwendung einer Dosen-Zustandsmaschine (dh eines Threads), um die Arbeit schneller oder mit geringerer Komplexität zu erledigen.