Ich habe bemerkt, dass alle Designs, die mir begegnet sind, Multi-Threading mit dem Aktor-Modus sein können - Trennen jedes Arbeitsmoduls in einen anderen Akteur und Verwenden einer Nachrichtenwarteschlange (für mich eine .NET ConcurrentQueue), um Nachrichten zu übergeben. Welche anderen guten Multi-Thread-Modelle gibt es?
Um noch mehr hinzuzufügen, gibt es verschiedene andere Multi-Threading-Modelle jenseits von CSP. Diese Wikipedia-Seite listet einige andere auf wie CCS , ACP und LOTOS . Das Lesen dieser Artikel deutet auf eine tiefe und dunkle Höhle hin, in der Akademiker herumstreifen und darauf warten, sich auf einen verirrten Softwareentwickler zu stürzen.
Das Problem ist, dass akademische Unklarheit oft einen vollständigen Mangel an Werkzeugen und Bibliotheken auf der praktischen, nutzbaren Ebene bedeutet. Es bedarf einer Menge Anstrengung, um eine fundierte, bewährte akademische Studie in eine Sammlung von Bibliotheken und Werkzeugen umzuwandeln. Es gibt wenig echten Anreiz für die breitere Software-Community, ein theoretisches Papier aufzunehmen und es in eine praktische Realität umzuwandeln.
Ich mag CSP, weil es eigentlich einfach ist, eine eigene CSP-Bibliothek basierend auf select () oder pselect () zu implementieren. Ich habe das schon einige Male gemacht (ich muss etwas über die Wiederverwendung von Code lernen), und die netten Leute an der Kent University haben JCSP für diejenigen, die Java mögen, zusammengestellt. Ich empfehle nicht, in Occam zu entwickeln (obwohl es immer noch möglich ist); Unterstützung und Wartbarkeit werden in Zukunft problematisch sein. CSP ist wahrscheinlich der einfachste Einstieg, und angesichts seiner guten Eigenschaften lohnt es sich.
Die Kommunikation sequentieller Prozesse ist meiner Meinung nach ein weitaus besseres Modell für die Nebenläufigkeit als das Akteurmodell. Es behandelt eine Reihe von Problemen mit dem Akteurmodell (und anderen Modellen) wie Deadlock, Livelock, Hungern. Werfen Sie einen Blick auf dieses und, noch praktischer, das .
Der Hauptunterschied ist wie folgt. Im Akteursmodell wird eine Nachricht asynchron gesendet. In CSP werden Nachrichten jedoch synchron gesendet. Der Sender kann erst senden, wenn der Empfänger empfangsbereit ist.
Diese einfache Einschränkung macht die Welt des Unterschieds. Wenn Sie ein falsches Design mit Deadlock-Potenzial haben, dann kann es im Actor-Modell vorkommen oder auch nicht (und das tritt normalerweise nur auf, wenn Sie dem Boss demonstrieren ...). In CSP wird der Deadlock jedoch immer auftreten, und Sie haben keinen Zweifel, dass Ihr Design falsch ist. Ok, du musst es noch reparieren, aber das ist OK; Probleme zu beheben, die Sie kennen haben, ist viel einfacher als der Versuch, erschöpfend auf das Fehlen von Problemen zu testen (Ihre einzige Wahl im Aktormodell).
Der strikt synchrone Ansatz von CSP scheint Probleme mit Antwortzeiten zu verursachen; Zum Beispiel befürchtet man, dass ein GUI-Thread sich nicht weiter bewegen kann, weil er keine Nachricht an einen beschäftigten Arbeitsthread senden konnte, der nicht bis zum "Lesen" gekommen ist. Sie müssen sicherstellen, dass die Arbeitslast auf genügend Threads verteilt ist, sodass alle innerhalb einer akzeptablen Zeitspanne auf neue Nachrichten warten können. CSP lässt dich nicht davonkommen. Das Akteurmodell tut sich jedoch nicht täuschen; Du entwickelst nur zukünftige Probleme.
In .NET ist eine ConcurrentQueue nicht das richtige Primitiv für CSP, es sei denn, Sie legen einen Synchronisierungsmechanismus oben drauf. Ich habe strenge Synchronisierung auch auf TCP-Sockets hinzugefügt. Tatsächlich schreibe ich in der Regel eine Art Bibliothek, die sowohl Sockets als auch Pipes abstrahiert, so dass es unwesentlich wird, ob ein "Prozess" (wie im CSP-Sprachgebrauch bekannt) ein Thread auf dieser Maschine oder ein ganz anderer Prozess ist auf einem anderen Computer am Ende einer Netzwerkverbindung. Nice - Scalabilty von Anfang an eingebaut.
Ich mache das seit 23 Jahren CSP, ich werde es nicht anders machen. Errichtet einige große Systeme mit Tausenden von Threads auf diese Weise.
== EDIT ==
Es scheint, dass diese Antwort immer noch Aufmerksamkeit erregt, also dachte ich, ich würde noch etwas hinzufügen. Für Windows-Entwickler gibt es den DataFlow -Namespace für den Task Parallel Bibliothek. Es muss separat heruntergeladen werden. Microsoft beschreibt es so: "Dieses Datenflussmodell fördert akteursbasierte Programmierung durch Bereitstellung von In-Process-Nachrichtenübergaben für grobkörnige Datenfluss- und Pipelining-Aufgaben." Ausgezeichnet! Es verwendet Klassen wie BufferBlocks als Kommunikationskanäle. Wichtig ist, dass ein BufferBlock eine BoundedCapacity -Eigenschaft, die standardmäßig auf" Unbounded "festgelegt ist, was dem Actor-Modell entspricht. Setzen Sie dies auf den Wert 1, und Sie haben es jetzt in einen CSP-artigen Kommunikationskanal umgewandelt.
@ Jeremy Friesner
Zukünftige Probleme
Was ich mit "Zukunftsproblemen" meinte, meinte ich damit, dass der Absender von Nachrichten in einem asynchronen System nicht weiß, ob der Empfänger tatsächlich mit der Nachfrage mithält. Der Absender weiß es nicht, weil er nur weiß, dass ein Nachrichtenpuffer die Nachricht akzeptiert hat. Der Transport darunter (z. B. tcp) geht dann mit der Aufgabe über, die Nachricht zu übertragen, wenn der Empfänger bereit ist, sie zu akzeptieren.
Es kann also sein, dass das System unter Stress nicht so funktioniert, wie es erforderlich ist, weil der Nachrichtentransport unweigerlich eine begrenzte Kapazität hat, Nachrichten zu absorbieren, die der Empfänger noch nicht akzeptieren kann. Der Absender findet dies erst heraus, nachdem das Problem bereits begonnen hat. Zu diesem Zeitpunkt könnte es zu spät sein, etwas dagegen zu tun.
Das Testen kann natürlich dieses Problem aufdecken, aber Sie müssen aufpassen, dass die Tests die Fähigkeit des Transports, Nachrichten aufzunehmen, wirklich erschöpft haben. Nur eine schnelle Explosion in voller Geschwindigkeit könnte täuschen.
Natürlich verursacht ein synchrones System einen Overhead ("bist du schon bereit?", "nein, noch nicht", "jetzt?", "ja!", "hier bist du dann"), was einfach nicht der Fall ist geschehen in einem asynchronen System. Im Durchschnitt wird das asynchrone System effizienter sein, könnte tatsächlich einen höheren Durchsatz haben, usw. Deshalb sind die meisten der Systeme der Welt tatsächlich asynchron, aber auch der Grund, warum Systeme nicht immer die volle Kapazität erreichen, die die rohen Netzwerkbandbreiten / Verarbeitungszeiten könnten dies nahelegen. Bei der Annäherung an die volle Kapazität neigen asynchrone Systeme meiner Meinung nach nicht dazu, elegant zu limitieren. Token Bus (nb nicht Token Ring) war ein gutes Beispiel für ein synchrones Netzwerk mit absolut zuverlässigem und deterministischem Durchsatz, war aber nur ein wenig langsamer als Ethernet und Token Ring ...
Nachdem ich bei meinen Problemen immer mit einem Übermaß an Bandbreite gesegnet war, habe ich den synchronen Weg aus Gründen der Erfolgssicherheit gewählt; Ich verliere nicht wirklich viel an Bandbreite, aber ich verliere Tonnen von Risiko, was gut ist.
Konvertiert von synchron zu asynchron
Vielleicht, aber es ist möglicherweise von geringem Wert. In einem synchronen System funktioniert es nur gemäß der Anforderung, wenn Sie die Arbeitsteilung zwischen Threads erfolgreich ausgeglichen haben. Das heißt, es gibt genug Threads, die die langsamen Bits ausführen, so dass die schnellen Bits nicht zurückgehalten werden. Das ist falsch und das System ist definitiv nicht schnell genug.
Aber nachdem Sie das getan haben, haben Sie ein System, in dem jede Komponente Nachrichten ohne Verzögerung weiterleiten kann, weil alles, was sie sendet, bereit und wartend ist (aufgrund Ihrer Fähigkeit und Ihres Urteilsvermögens, die Arbeitsbelastung auszugleichen). Wenn Sie also in einen asynchronen Nachrichtentransport konvertiert haben, sparen Sie nur einen Bruchteil der Zeit beim Transport dieser Nachrichten. Sie nehmen keine Änderungen vor, die dazu führen, dass die Workloads schneller verarbeitet werden. Wenn jedoch die Einsparung von Bandbreite das Ziel ist, lohnt es sich vielleicht.
Natürlich kann es schwierig sein, dieses Balancing durchzuführen, und der Umgang mit Variabilitäten wie HDD-Zugriffszeiten, Netzwerken usw. kann schwierig zu bewältigen sein. Ich musste oft ein "nächstes verfügbares" Workload-Sharing-Schema implementieren. Aber in Echtzeit arbeiten Signalverarbeitungssysteme wie die, mit denen ich zusammenarbeite, im Grunde mit einem sehr zuverlässigen Transport wie OpenVPX's RapidIO, Sie machen nur Summen mit Daten (nicht mit Datenbanken, Disketten usw.) und der Datenraten sind sehr hoch (1GByte / Sek. ist in diesen Tagen vollkommen machbar, und tatsächlich habe ich mit Datenraten gearbeitet, die vor 13 Jahren so hoch waren; das war schlechte Arbeit). Streng synchron zu sein bedeutet, dass Sie entweder mit der Datenrate auf jeden Fall mithalten oder definitiv nicht. Mit asynchron ist es vielleicht mehr ...
Echtzeit-OS für alle!
Ein Echtzeit-Betriebssystem ist auch eine essentielle Komponente, und heutzutage scheint es das PREEMPT_RT-Patch-Set für Linux zu sein, das für viele Leute im Handel funktioniert. Redhat macht einen Prepack Spin (RedHat MRG), aber für ein Freebie ist Scientific Linux von den netten Leuten am CERN gut und kostenlos! Ich vermute stark, dass viele Systeme bei Verwendung von PREEMPT_RT viel reibungsloser an ihren Kapazitätsgrenzen arbeiten würden - sie glättet die Dinge sehr gut.
Concurrency ist ein faszinierendes Thema mit vielen Implementierungsansätzen. Die grundlegende Frage lautet: "Wie koordiniere ich parallele Berechnungen?" .
Einige Modelle der Parallelität sind:
Futures auch bekannt als Promises oder Tasks sind Objekte, die als Proxys für ein asynchron berechnetes Ergebnis fungieren. Wenn der Wert tatsächlich für eine Berechnung benötigt wird, bleibt der Thread stehen, bis die Berechnung abgeschlossen ist und somit die Synchronisation erreicht ist.
Futures sind das bevorzugte Concurrency-Modell für .NET und ES6 .
Der Software Transactional Memory (STM) synchronisiert den Zugriff auf gemeinsam genutzten Speicher (ähnlich wie Sperren), indem er Aktionen in Transaktionen gruppiert. Jede einzelne Transaktion sieht nur eine einzige Ansicht des gemeinsamen Speichers und ist atomar. Dies ist konzeptionell ähnlich wie viele Datenbanken mit Nebenläufigkeit umgehen.
STM ist das bevorzugte Nebenläufigkeitsmodell für Clojure und Haskell .
Das Actor-Modell konzentriert sich auf die Weitergabe von Nachrichten. Ein Akteur empfängt eine Nachricht und kann entscheiden, eine Nachricht als Antwort zu senden, andere Akteure zu spawnen, lokale Änderungen vorzunehmen usw. Dies ist wahrscheinlich das am wenigsten eng gekoppelte Modell von diesen, da die Akteure nur Nachrichten austauschen und nichts anderes.
Das Actor Model ist das bevorzugte Concurrency-Modell für Erlang und Rust .
Beachten Sie, dass die meisten Sprachen im Gegensatz zu den oben genannten Sprachen keine Kanonen- oder bevorzugten Gleichzeitigkeitsmodelle haben und selbst die Sprachen, die eine starke Präferenz für ein Modell aufweisen, haben normalerweise die anderen als Bibliotheken implementiert.
Meine persönliche Meinung ist, dass Futures STM und Actors in der Einfachheit der Verwendung und Argumentation outclass, aber keines dieser Modelle ist von Natur aus "falsch" und ich kann keine Nachteile für beide denken. Sie können das verwenden, was Sie ohne Konsequenzen vorziehen.
Das allgemeinste Modell für die parallele Verarbeitung ist Petri Nets . Sie stellt die Berechnung als reinen Datenabhängigkeitsgraphen dar, der maximale Parallelität ausdrückt. Alle anderen Modelle stammen daraus.
Dataflow Computing-Modell Ссылка , Ссылка ist fast so mächtig. Es beschränkt Petri-Netzplätze auf nur einen Ausgangsbogen. In der Praxis ist dies nützlich, da Orte mit mehreren Ausgabebögen schwer zu implementieren sind, Indeterminismus verursachen und nur selten benötigt werden.
Das Actor-Modell ist ein Datenflussmodell, bei dem Knoten nur zwei Eingangskanten haben können - eine für Eingangsnachrichten und eine für den Status des Aktors. Dies ist eine ernsthafte Einschränkung, wenn Sie Funktionen mit Nebeneffekten und mehr als einem Argument programmieren wollen.
Tags und Links multithreading design-patterns concurrency