Entwurfsprinzipien und -muster für Anwendungen mit hoher Gleichzeitigkeit

1. Übersicht

In diesem Lernprogramm werden einige der Entwurfsprinzipien und -muster erläutert, die im Laufe der Zeit festgelegt wurden, um Anwendungen mit hoher Gleichzeitigkeit zu erstellen.

Es ist jedoch anzumerken, dass das Entwerfen einer gleichzeitigen Anwendung ein weites und komplexes Thema ist und daher kein Tutorial behaupten kann, in seiner Behandlung erschöpfend zu sein. Was wir hier behandeln werden, sind einige der beliebten Tricks, die oft angewendet werden!

2. Grundlagen der Parallelität

Bevor wir fortfahren, sollten wir einige Zeit damit verbringen, die Grundlagen zu verstehen. Zunächst müssen wir unser Verständnis dessen klären, was wir als gleichzeitiges Programm bezeichnen. Wir beziehen uns auf ein Programm, das gleichzeitig ausgeführt wird, wenn mehrere Berechnungen gleichzeitig ausgeführt werden .

Beachten Sie nun, dass wir erwähnt haben, dass Berechnungen zur gleichen Zeit ausgeführt werden - das heißt, sie werden zur gleichen Zeit ausgeführt. Sie können jedoch gleichzeitig ausgeführt werden oder nicht. Es ist wichtig, den Unterschied zu verstehen, da gleichzeitig ausgeführte Berechnungen als parallel bezeichnet werden .

2.1. Wie erstelle ich gleichzeitige Module?

Es ist wichtig zu verstehen, wie wir gleichzeitig Module erstellen können. Es gibt zahlreiche Optionen, aber wir werden uns hier auf zwei beliebte Optionen konzentrieren:

  • Prozess : Ein Prozess ist eine Instanz eines laufenden Programms, das von anderen Prozessen auf demselben Computer isoliert ist . Jeder Prozess auf einer Maschine hat seine eigene isolierte Zeit und seinen eigenen Raum. Daher ist es normalerweise nicht möglich, Speicher zwischen Prozessen zu teilen, und sie müssen durch Weiterleiten von Nachrichten kommunizieren.
  • Thread : Ein Thread ist dagegen nur ein Segment eines Prozesses . Es können mehrere Threads in einem Programm vorhanden sein, die denselben Speicherplatz gemeinsam nutzen. Jeder Thread hat jedoch einen eindeutigen Stapel und eine eindeutige Priorität. Ein Thread kann nativ (vom Betriebssystem nativ geplant) oder grün (von einer Laufzeitbibliothek geplant) sein.

2.2. Wie interagieren gleichzeitige Module?

Es ist ideal, wenn gleichzeitige Module nicht kommunizieren müssen, aber das ist oft nicht der Fall. Daraus ergeben sich zwei Modelle der gleichzeitigen Programmierung:

  • Gemeinsamer Speicher : In diesem Modell interagieren gleichzeitige Module, indem sie gemeinsam genutzte Objekte im Speicher lesen und schreiben . Dies führt häufig zu einer Verschachtelung gleichzeitiger Berechnungen, was zu Rennbedingungen führt. Daher kann es nicht deterministisch zu falschen Zuständen führen.
  • Nachrichtenübermittlung : In diesem Modell interagieren gleichzeitige Module, indem sie Nachrichten über einen Kommunikationskanal aneinander weiterleiten . Hier verarbeitet jedes Modul eingehende Nachrichten nacheinander. Da es keinen gemeinsamen Status gibt, ist das Programmieren relativ einfach, aber dies ist immer noch nicht frei von Rennbedingungen!

2.3. Wie werden gleichzeitige Module ausgeführt?

Es ist schon eine Weile her, dass Moores Gesetz in Bezug auf die Taktrate des Prozessors gegen eine Wand gestoßen ist. Da wir wachsen müssen, haben wir stattdessen begonnen, mehrere Prozessoren auf denselben Chip zu packen, die oft als Multicore-Prozessoren bezeichnet werden. Es ist jedoch nicht üblich, von Prozessoren mit mehr als 32 Kernen zu hören.

Jetzt wissen wir, dass ein einzelner Kern jeweils nur einen Thread oder einen Befehlssatz ausführen kann. Die Anzahl der Prozesse und Threads kann jedoch Hunderte bzw. Tausende betragen. Wie funktioniert es wirklich? Hier simuliert das Betriebssystem für uns die Parallelität . Das Betriebssystem erreicht dies durch Time-Slicing - was effektiv bedeutet, dass der Prozessor häufig, unvorhersehbar und nicht deterministisch zwischen Threads wechselt.

3. Probleme bei der gleichzeitigen Programmierung

Wenn wir Prinzipien und Muster diskutieren, um eine gleichzeitige Anwendung zu entwerfen, ist es ratsam, zunächst die typischen Probleme zu verstehen.

Unsere Erfahrung mit der gleichzeitigen Programmierung umfasst zum großen Teil die Verwendung nativer Threads mit gemeinsamem Speicher . Daher werden wir uns auf einige der allgemeinen Probleme konzentrieren, die sich daraus ergeben:

  • Gegenseitiger Ausschluss (Synchronisationsprimitive) : Interleaving- Threads müssen exklusiven Zugriff auf den gemeinsam genutzten Status oder Speicher haben, um die Richtigkeit der Programme sicherzustellen . Die Synchronisation gemeinsam genutzter Ressourcen ist eine beliebte Methode, um einen gegenseitigen Ausschluss zu erreichen. Es stehen mehrere Synchronisationsprimitive zur Verfügung, z. B. eine Sperre, ein Monitor, ein Semaphor oder ein Mutex. Die Programmierung zum gegenseitigen Ausschluss ist jedoch fehleranfällig und kann häufig zu Leistungsengpässen führen. Es gibt mehrere gut diskutierte Probleme im Zusammenhang damit, wie Deadlock und Livelock.
  • Kontextwechsel (Heavyweight-Threads) : Jedes Betriebssystem bietet native, wenn auch unterschiedliche Unterstützung für gleichzeitige Module wie Prozess und Thread. Wie bereits erwähnt, besteht einer der grundlegenden Dienste, die ein Betriebssystem bereitstellt, darin, Threads so zu planen, dass sie durch Time-Slicing auf einer begrenzten Anzahl von Prozessoren ausgeführt werden. Dies bedeutet effektiv, dass Threads häufig zwischen verschiedenen Zuständen gewechselt werden . Dabei muss ihr aktueller Status gespeichert und fortgesetzt werden. Dies ist eine zeitaufwändige Aktivität, die sich direkt auf den Gesamtdurchsatz auswirkt.

4. Entwurfsmuster für hohe Parallelität

Nachdem wir nun die Grundlagen der gleichzeitigen Programmierung und die darin enthaltenen allgemeinen Probleme verstanden haben, ist es an der Zeit, einige der allgemeinen Muster zur Vermeidung dieser Probleme zu verstehen. Wir müssen wiederholen, dass die gleichzeitige Programmierung eine schwierige Aufgabe ist, die viel Erfahrung erfordert. Das Befolgen einiger der festgelegten Muster kann daher die Aufgabe erleichtern.

4.1. Schauspielerbasierte Parallelität

Das erste Design, das wir in Bezug auf die gleichzeitige Programmierung diskutieren werden, heißt Actor Model. Dies ist ein mathematisches Modell der gleichzeitigen Berechnung, das im Grunde alles als Akteur behandelt . Akteure können Nachrichten aneinander weitergeben und als Antwort auf eine Nachricht lokale Entscheidungen treffen. Dies wurde zuerst von Carl Hewitt vorgeschlagen und hat eine Reihe von Programmiersprachen inspiriert.

Scalas Hauptkonstrukt für die gleichzeitige Programmierung sind Schauspieler. Schauspieler sind normale Objekte in Scala, die wir durch Instanziieren der Actor- Klasse erstellen können . Darüber hinaus bietet die Scala Actors-Bibliothek viele nützliche Schauspieleroperationen:

class myActor extends Actor { def act() { while(true) { receive { // Perform some action } } } }

Im obigen Beispiel wird der Akteur durch einen Aufruf der Empfangsmethode in einer Endlosschleife angehalten, bis eine Nachricht eintrifft. Bei der Ankunft wird die Nachricht aus dem Postfach des Schauspielers entfernt und die erforderlichen Maßnahmen ergriffen.

Das Akteurmodell beseitigt eines der grundlegenden Probleme bei der gleichzeitigen Programmierung - den gemeinsamen Speicher . Akteure kommunizieren über Nachrichten, und jeder Akteur verarbeitet Nachrichten aus seinen exklusiven Postfächern nacheinander. Wir führen jedoch Akteure über einen Thread-Pool aus. Und wir haben gesehen, dass native Threads schwergewichtig und daher in ihrer Anzahl begrenzt sein können.

Es gibt natürlich auch andere Muster, die uns hier helfen können - wir werden diese später behandeln!

4.2. Ereignisbasierte Parallelität

Ereignisbasierte Designs befassen sich explizit mit dem Problem, dass das Erstellen und Betreiben nativer Threads teuer ist. Eines der ereignisbasierten Designs ist die Ereignisschleife. Die Ereignisschleife arbeitet mit einem Ereignisanbieter und einer Reihe von Ereignishandlern zusammen. In dieser Konfiguration blockiert die Ereignisschleife den Ereignisanbieter und sendet ein Ereignis bei Ankunft an einen Ereignishandler .

Grundsätzlich ist die Ereignisschleife nichts anderes als ein Ereignisverteiler! Die Ereignisschleife selbst kann nur auf einem einzigen nativen Thread ausgeführt werden. Was passiert also wirklich in einer Ereignisschleife? Schauen wir uns als Beispiel den Pseudocode einer wirklich einfachen Ereignisschleife an:

while(true) { events = getEvents(); for(e in events) processEvent(e); }

Grundsätzlich besteht unsere Ereignisschleife lediglich darin, kontinuierlich nach Ereignissen zu suchen und diese zu verarbeiten, wenn sie gefunden werden. Der Ansatz ist wirklich einfach, profitiert jedoch von einem ereignisgesteuerten Design.

Durch das Erstellen gleichzeitiger Anwendungen mit diesem Design erhält die Anwendung mehr Kontrolle. Außerdem werden einige der typischen Probleme der Multithread-Anwendungen beseitigt, z. B. Deadlock.

JavaScript implementiert die Ereignisschleife, um asynchrone Programmierung zu ermöglichen . Es verwaltet einen Aufrufstapel, um alle auszuführenden Funktionen zu verfolgen. Außerdem wird eine Ereigniswarteschlange zum Senden neuer Funktionen zur Verarbeitung verwaltet. Die Ereignisschleife überprüft ständig den Aufrufstapel und fügt neue Funktionen aus der Ereigniswarteschlange hinzu. Alle asynchronen Aufrufe werden an die Web-APIs gesendet, die normalerweise vom Browser bereitgestellt werden.

Die Ereignisschleife selbst kann von einem einzelnen Thread ausgeführt werden, die Web-APIs stellen jedoch separate Threads bereit.

4.3. Nicht blockierende Algorithmen

Bei nicht blockierenden Algorithmen führt das Anhalten eines Threads nicht zum Anhalten anderer Threads. Wir haben gesehen, dass wir nur eine begrenzte Anzahl nativer Threads in unserer Anwendung haben können. Nun, ein Algorithmus , dass die Blöcke in einem Thread bringt offensichtlich den Durchsatz erheblich nach unten und verhindert , dass wir den Aufbau hoch gleichzeitige Anwendungen.

Nicht blockierende Algorithmen verwenden ausnahmslos das atomare Vergleichs- und Austausch-Grundelement, das von der zugrunde liegenden Hardware bereitgestellt wird . Dies bedeutet, dass die Hardware den Inhalt eines Speicherorts mit einem bestimmten Wert vergleicht und den Wert nur dann auf einen neuen angegebenen Wert aktualisiert, wenn er identisch ist. Dies mag einfach aussehen, bietet uns jedoch effektiv eine atomare Operation, die andernfalls eine Synchronisation erfordern würde.

Dies bedeutet, dass wir neue Datenstrukturen und Bibliotheken schreiben müssen, die diese atomare Operation nutzen. Dies hat uns eine Vielzahl von wartungs- und sperrfreien Implementierungen in mehreren Sprachen ermöglicht. Java verfügt über mehrere nicht blockierende Datenstrukturen wie AtomicBoolean , AtomicInteger , AtomicLong und AtomicReference .

Stellen Sie sich eine Anwendung vor, in der mehrere Threads versuchen, auf denselben Code zuzugreifen:

boolean open = false; if(!open) { // Do Something open=false; }

Der obige Code ist eindeutig nicht threadsicher und sein Verhalten in einer Umgebung mit mehreren Threads kann unvorhersehbar sein. Wir haben hier entweder die Möglichkeit, diesen Code mit einer Sperre zu synchronisieren oder eine atomare Operation zu verwenden:

AtomicBoolean open = new AtomicBoolean(false); if(open.compareAndSet(false, true) { // Do Something }

Wie wir sehen können, hilft uns die Verwendung einer nicht blockierenden Datenstruktur wie AtomicBoolean dabei , threadsicheren Code zu schreiben, ohne die Nachteile von Sperren zu berücksichtigen !

5. Unterstützung in Programmiersprachen

Wir haben gesehen, dass es mehrere Möglichkeiten gibt, ein gleichzeitiges Modul zu erstellen. Während die Programmiersprache einen Unterschied macht, unterstützt das zugrunde liegende Betriebssystem das Konzept hauptsächlich. Da die von nativen Threads unterstützte threadbasierte Parallelität jedoch in Bezug auf die Skalierbarkeit neue Wände erreicht , benötigen wir immer neue Optionen.

Die Implementierung einiger der im letzten Abschnitt diskutierten Entwurfspraktiken hat sich als effektiv erwiesen. Wir müssen jedoch bedenken, dass dies die Programmierung als solche erschwert. Was wir wirklich brauchen, ist etwas, das die Leistung einer threadbasierten Parallelität ohne die damit verbundenen unerwünschten Effekte bietet.

Eine Lösung, die uns zur Verfügung steht, sind grüne Fäden. Grüne Threads sind Threads, die von der Laufzeitbibliothek geplant werden, anstatt nativ vom zugrunde liegenden Betriebssystem geplant zu werden. Dies beseitigt zwar nicht alle Probleme bei der threadbasierten Parallelität, kann jedoch in einigen Fällen zu einer besseren Leistung führen.

Jetzt ist es nicht trivial, grüne Threads zu verwenden, es sei denn, die von uns gewählte Programmiersprache unterstützt dies. Nicht jede Programmiersprache verfügt über diese integrierte Unterstützung. Auch das, was wir locker als grüne Threads bezeichnen, kann von verschiedenen Programmiersprachen auf einzigartige Weise implementiert werden. Sehen wir uns einige dieser Optionen an, die uns zur Verfügung stehen.

5.1. Goroutinen in Go

Goroutinen in der Programmiersprache Go sind leichte Threads. Sie bieten Funktionen oder Methoden, die gleichzeitig mit anderen Funktionen oder Methoden ausgeführt werden können. Goroutinen sind extrem billig, da sie zunächst nur wenige Kilobyte Stapelgröße einnehmen .

Am wichtigsten ist, dass Goroutinen mit einer geringeren Anzahl nativer Threads gemultiplext werden. Darüber hinaus kommunizieren Goroutinen über Kanäle miteinander, wodurch der Zugriff auf den gemeinsam genutzten Speicher vermieden wird. Wir bekommen so ziemlich alles, was wir brauchen, und raten Sie mal - ohne etwas zu tun!

5.2. Prozesse in Erlang

In Erlang wird jeder Ausführungsthread als Prozess bezeichnet. Aber es ist nicht ganz so, wie wir es bisher besprochen haben! Erlang-Prozesse sind leicht und haben einen geringen Speicherbedarf. Sie können mit geringem Planungsaufwand schnell erstellt und entsorgt werden .

Unter der Haube sind Erlang-Prozesse nichts anderes als Funktionen, für die die Laufzeit die Planung übernimmt. Darüber hinaus teilen Erlang-Prozesse keine Daten und kommunizieren durch Nachrichtenübermittlung miteinander. Dies ist der Grund, warum wir diese „Prozesse“ überhaupt nennen!

5.3. Fasern in Java (Vorschlag)

Die Geschichte der Parallelität mit Java war eine kontinuierliche Entwicklung. Java hatte zunächst Unterstützung für grüne Threads, zumindest für Solaris-Betriebssysteme. Dies wurde jedoch aufgrund von Hürden eingestellt, die über den Rahmen dieses Tutorials hinausgehen.

Seitdem dreht sich bei der Parallelität in Java alles um native Threads und wie man intelligent mit ihnen arbeitet! Aus offensichtlichen Gründen wird es in Java möglicherweise bald eine neue Parallelitätsabstraktion geben, die als Glasfaser bezeichnet wird. Project Loom schlägt vor, Fortsetzungen zusammen mit Fasern einzuführen, was die Art und Weise ändern kann, wie wir gleichzeitige Anwendungen in Java schreiben !

Dies ist nur ein kleiner Einblick in das, was in verschiedenen Programmiersprachen verfügbar ist. Es gibt weitaus interessantere Möglichkeiten, wie andere Programmiersprachen versucht haben, mit Parallelität umzugehen.

Darüber hinaus ist anzumerken, dass eine Kombination der im letzten Abschnitt beschriebenen Entwurfsmuster zusammen mit der Programmiersprachenunterstützung für eine Green-Thread-ähnliche Abstraktion beim Entwerfen von Anwendungen mit gleichzeitigem Zugriff äußerst leistungsfähig sein kann.

6. Anwendungen mit hoher Parallelität

In einer realen Anwendung interagieren häufig mehrere Komponenten über das Kabel miteinander. Wir greifen normalerweise über das Internet darauf zu und es besteht aus mehreren Diensten wie Proxy-Dienst, Gateway, Webdienst, Datenbank, Verzeichnisdienst und Dateisystemen.

Wie stellen wir in solchen Situationen eine hohe Parallelität sicher? Lassen Sie uns einige dieser Ebenen und die Optionen zum Erstellen einer hochgradig gleichzeitigen Anwendung untersuchen.

Wie wir im vorherigen Abschnitt gesehen haben, besteht der Schlüssel zum Erstellen von Anwendungen mit hoher Parallelität darin, einige der dort diskutierten Entwurfskonzepte zu verwenden. Wir müssen die richtige Software für den Job auswählen - diejenigen, die bereits einige dieser Praktiken beinhalten.

6.1. Webebene

Das Web ist normalerweise die erste Ebene, auf der Benutzeranforderungen eingehen, und die Bereitstellung einer hohen Parallelität ist hier unvermeidlich. Mal sehen, welche Optionen es gibt:

  • Node (auch NodeJS oder Node.js genannt) ist eine plattformübergreifende Open-Source-JavaScript-Laufzeit, die auf der V8-JavaScript-Engine von Chrome basiert. Der Knoten funktioniert recht gut bei der Verarbeitung von asynchronen E / A-Vorgängen. Der Grund, warum Node es so gut macht, ist, dass es eine Ereignisschleife über einen einzelnen Thread implementiert. Die Ereignisschleife behandelt mit Hilfe von Rückrufen alle Blockierungsvorgänge wie E / A asynchron.
  • nginx ist ein Open-Source-Webserver, den wir unter anderem häufig als Reverse-Proxy verwenden . Der Grund, warum nginx eine hohe Parallelität bietet, besteht darin, dass es einen asynchronen, ereignisgesteuerten Ansatz verwendet. nginx arbeitet mit einem Master-Prozess in einem einzelnen Thread. Der Master-Prozess verwaltet Worker-Prozesse, die die eigentliche Verarbeitung ausführen. Daher verarbeiten die Worker-Prozesse jede Anforderung gleichzeitig.

6.2. Anwendungsschicht

Beim Entwerfen einer Anwendung stehen verschiedene Tools zur Verfügung, mit denen wir eine hohe Parallelität erzielen können. Lassen Sie uns einige dieser Bibliotheken und Frameworks untersuchen, die uns zur Verfügung stehen:

  • Akka ist ein in Scala geschriebenes Toolkit zum Erstellen von hochkonkurrierenden und verteilten Anwendungen auf der JVM. Akkas Ansatz zum Umgang mit Parallelität basiert auf dem zuvor diskutierten Akteurmodell. Akka schafft eine Schicht zwischen den Akteuren und den zugrunde liegenden Systemen. Das Framework behandelt die Komplexität des Erstellens und Planens von Threads sowie des Empfangens und Versendens von Nachrichten.
  • Project Reactor ist eine reaktive Bibliothek zum Erstellen nicht blockierender Anwendungen auf der JVM. Es basiert auf der Reactive Streams-Spezifikation und konzentriert sich auf eine effiziente Nachrichtenübermittlung und Bedarfsverwaltung (Gegendruck). Reaktorbetreiber und Planer können hohe Durchsatzraten für Nachrichten aufrechterhalten. Mehrere beliebte Frameworks bieten Reaktorimplementierungen, darunter Spring WebFlux und RSocket.
  • Netty ist ein asynchrones, ereignisgesteuertes Netzwerkanwendungsframework. Wir können Netty verwenden, um hochkonkurrierende Protokollserver und -clients zu entwickeln. Netty nutzt NIO, eine Sammlung von Java-APIs, die eine asynchrone Datenübertragung über Puffer und Kanäle ermöglichen. Es bietet uns mehrere Vorteile wie einen besseren Durchsatz, eine geringere Latenz, einen geringeren Ressourcenverbrauch und die Minimierung unnötiger Speicherkopien.

6.3. Datenschicht

Schließlich ist keine Anwendung ohne ihre Daten vollständig, und die Daten stammen aus einem dauerhaften Speicher. Wenn wir über eine hohe Parallelität in Bezug auf Datenbanken sprechen, liegt der Schwerpunkt weiterhin auf der NoSQL-Familie. Dies ist hauptsächlich auf die lineare Skalierbarkeit zurückzuführen, die NoSQL-Datenbanken bieten können, ist jedoch in relationalen Varianten schwer zu erreichen. Schauen wir uns zwei beliebte Tools für die Datenschicht an:

  • Cassandra ist eine kostenlose und Open-Source-verteilte NoSQL-Datenbank , die hohe Verfügbarkeit, hohe Skalierbarkeit und Fehlertoleranz für Standardhardware bietet. Cassandra bietet jedoch keine ACID-Transaktionen an, die mehrere Tabellen umfassen. Wenn unsere Anwendung keine starke Konsistenz und Transaktionen erfordert, können wir von Cassandras Operationen mit geringer Latenz profitieren.
  • Kafka ist eine verteilte Streaming-Plattform . Kafka speichert einen Datenstrom in Kategorien, die als Themen bezeichnet werden. Es kann sowohl Herstellern als auch Verbrauchern der Aufzeichnungen eine lineare horizontale Skalierbarkeit bieten und gleichzeitig eine hohe Zuverlässigkeit und Haltbarkeit bieten. Partitionen, Replikate und Broker sind einige der grundlegenden Konzepte, auf denen eine massiv verteilte Parallelität bereitgestellt wird.

6.4. Cache-Ebene

Nun, keine Webanwendung in der modernen Welt, die eine hohe Parallelität anstrebt, kann es sich leisten, jedes Mal auf die Datenbank zuzugreifen. Daher können wir einen Cache auswählen - vorzugsweise einen In-Memory-Cache, der unsere hochgradig gleichzeitigen Anwendungen unterstützt:

  • Hazelcast ist eine verteilte, cloudfreundliche In-Memory-Objektspeicher- und Rechenmaschine, die eine Vielzahl von Datenstrukturen wie Map , Set , List , MultiMap , RingBuffer und HyperLogLog unterstützt . Es verfügt über eine integrierte Replikation und bietet hohe Verfügbarkeit und automatische Partitionierung.
  • Redis ist ein speicherinterner Datenstrukturspeicher, den wir hauptsächlich als Cache verwenden . Es bietet eine speicherinterne Schlüsselwertdatenbank mit optionaler Haltbarkeit. Die unterstützten Datenstrukturen umfassen Zeichenfolgen, Hashes, Listen und Mengen. Redis verfügt über eine integrierte Replikation und bietet hohe Verfügbarkeit und automatische Partitionierung. Für den Fall, dass wir keine Persistenz benötigen, kann Redis uns einen funktionsreichen, vernetzten In-Memory-Cache mit hervorragender Leistung anbieten.

Natürlich haben wir kaum die Oberfläche dessen zerkratzt, was uns zur Verfügung steht, um eine hochgradig gleichzeitige Anwendung zu erstellen. Es ist wichtig zu beachten, dass unsere Anforderungen mehr als die verfügbare Software uns bei der Erstellung eines geeigneten Designs unterstützen sollten. Einige dieser Optionen sind möglicherweise geeignet, während andere möglicherweise nicht geeignet sind.

Und vergessen wir nicht, dass es viel mehr Optionen gibt, die möglicherweise besser für unsere Anforderungen geeignet sind.

7. Fazit

In diesem Artikel haben wir die Grundlagen der gleichzeitigen Programmierung erläutert. Wir haben einige der grundlegenden Aspekte der Parallelität und die Probleme, zu denen sie führen kann, verstanden. Außerdem haben wir einige Entwurfsmuster durchgearbeitet, mit denen wir die typischen Probleme bei der gleichzeitigen Programmierung vermeiden können.

Schließlich haben wir einige der Frameworks, Bibliotheken und Software durchgesehen, die uns zum Erstellen einer hochkonkurrierenden End-to-End-Anwendung zur Verfügung stehen.