Richtlinie: Parallelität
Diese Richtlinie hilft Entwicklern, die beste Methode für die Bewältigung von Parallelitätsanforderungen in einem Softwaresystem auszuwählen.
Beziehungen
Hauptbeschreibung

Einführung

Die Kunst eines guten Designs ist, die Methode zu wählen, mit denen die Anforderungen am besten erfüllt werden. Die Kunst eines guten parallelen Systemdesigns ist häufig die, die Methode zu wählen, mit denen die Parallelitätsanforderungen am einfachsten erfüllt werden. Eine der ersten Regeln für Designer ist zu vermeiden, das "Rad neu zu erfinden". Für die Lösung der meisten Probleme wurden bereits taugliche Designmuster und Designidiome entwickelt. In Anbetracht der Komplexität paralleler System ist es sinnvoll, bewährte Lösungen zu verwenden und das Design so einfach wie möglich zu gestalten.

Ansätze für Parallelität

Parallele Aufgaben, die vollständig innerhalb eines Computers ausgeführt werden können, werden Ausführungs-Threads genannt. Wie alle parallelen Aufgaben sind Ausführungs-Threads ein abstraktes Konzept, da sie in zeitlicher Abfolge ausgeführt werden. Die einzige Möglichkeit, einen Ausführungs-Thread physisch zu erfassen, besteht darin, seinen Zustand zu einem bestimmten Zeitpunkt darzustellen.

Der direkteste Weg für die Darstellung paralleler Aufgaben mit Computern ist, jeder Aufgabe einen gesonderten Computer zuzuordnen. Dies ist gewöhnlich jedoch zu kostenintensiv und trägt nicht immer zur Konfliktlösung bei. Deshalb werden im Allgemeinen mehrere Aufgaben durch irgendeine Form von Multitasking in demselben physischen Prozessor unterstützt. In diesem Fall werden der Prozessor und die zugehörigen Ressourcen wie Hauptspeicher und Busse gemeinsam genutzt. (Leider kann diese gemeinsame Nutzung von Ressourcen zu neuen Konflikten führen, die beim ursprünglichen Problem nicht vorlagen.)

Die gebräuchlichste Form des Multitasking ist die Bereitstellung eines "virtuellen" Prozessors für jede Aufgabe. Dieser virtuelle Prozessor wird normalerweise als Prozess oder Task bezeichnet. Normalerweise hat jeder Prozess einen eigenen Adressraum, der sich logisch vom Adressraum anderer virtueller Prozessoren unterscheidet. Dadurch wird verhindert, dass miteinander in Konflikt stehende Prozesse versehentlich den Hauptspeicher des jeweils anderen überschreiben. Leider ist der Aufwand des physischen Prozessors für jeden Prozesswechsel häufig unverhältnismäßig hoch. Ein Prozesswechsel bedeutet die Auslagerung von Registersets in der CPU (Kontextwechsel), die selbst mit modernen Hochgeschwindigkeitsprozessoren mehrere Hundert Mikrosekunde dauern kann.

Zur Verringerung dieses Aufwands bieten viele Betriebssysteme die Möglichkeit, mehrere Lightweight-Threads in einem einzelnen Prozess auszuführen. Die Threads in einem Prozess verwenden denselben Adressraum wie der Prozess. Dies verringert den Aufwand für den Kontextwechsel, erhöht aber die Wahrscheinlichkeit von Speicherkonflikten.

Für einige durchsatzstarke Anwendungen kann selbst der Aufwand für Lightweight-Thread-Switching unangemessen hoch sein. In solchen Situationen wird gewöhnlich eine noch schlankere Form des Multitasking verwendet, bei der einige spezielle Features der Anwendung genutzt werden.

Die Parallelitätsanforderungen des Systems können dramatische Auswirkungen auf die Systemarchitektur haben. Die Entscheidung, Funktionalität von einer Einzelprozessarchitektur auf eine Mehrprozessarchitektur umzustellen, kann erhebliche Änderungen an der Systemstruktur mit sich bringen und das in vielerlei Hinsicht. Es müssen möglicherweise zusätzliche Mechanismen (z. B. Fernprozeduraufrufe) eingeführt werden, die die Architektur des Systems wesentlich verändern können.

Es müssen Anforderungen an die Systemverfügbarkeit sowie der zusätzliche Aufwand für die Verwaltung zusätzlicher Prozesse und Threads berücksichtigt werden.

Wie bei den meisten Architekturentscheidungen werden beim Ändern der Prozessarchitektur einige Probleme gegen andere eingetauscht:

Ansatz

Vorteile

Nachteile

Einzelprozess, keine Threads
  • Einfachheit
  • Schnelle prozessinterne Nachrichtenübertragung
  • Gleichmäßige Lastverteilung nur schwer möglich
  • Skalierung auf mehrere Prozessoren nicht möglich
Einzelprozess, mehrere Threads
  • Schnelle prozessinterne Nachrichtenübertragung
  • Multitasking ohne Interprozesskommunikation
  • Besseres Multitasking ohne Aufwand für 'Heavyweight'-Prozesse
  • Anwendung muss 'Thread-sicher' sein
  • Betriebssystem muss eine effiziente Thread-Verwaltung haben
  • Aspekte in Bezug auf gemeinsam genutzten Speicher müssen berücksichtigt werden.
Mehrere Prozesse
  • Gute Skalierung beim Hinzufügen von Prozessoren
  • Relativ einfache Verteilung auf Knoten
  • Sensibilität bezüglich der Prozessgrenzen: übermäßige Interprozesskommunikation beeinträchtigt die Leistung
  • Hohe Kosten für Auslagern und Kontextwechsel
  • Schwieriges Design

Ein typischer evolutionärer Weg ist, mit einer Einzelprozessarchitektur zu beginnen und anschließend Prozesse für Verhaltensgruppen hinzuzufügen, die gleichzeitig auftreten müssen. Innerhalb dieser weiter gefassten Gruppierungen können Sie zusätzliche Parallelitätsanforderungen berücksichtigen und Threads innerhalb von Prozessen hinzufügen, um die Parallelität zu erhöhen.

Zunächst werden mit einem speziellen Scheduler für aktive Objekte viele aktive Objekte einer einzelnen Betriebssystem-Task oder einem Betriebssystem-Thread zugeordnet. Auf diese Weise können Sie gewöhnlich eine sehr schlanke Simulation der Parallelität erreichen, obwohl es mit einer einzelnen Betriebssystem-Task bzw. einem einzelnen Betriebssystem-Thread nicht möglich sein wird, Maschinen mit mehreren CPUs zu nutzen. Die Schlüsselentscheidung ist, blockierendes Verhalten in gesonderten Threads zu isolieren, so dass blockierendes Verhalten nicht zu einem Engpass wird. Dies führt zu einer Absonderung aktiver Objekte mit blockierendem Verhalten in eigene Betriebssystem-Threads.

In Echtzeitsystemen gilt diese Überlegung gleichermaßen für Kapseln. Jede Kapsel hat einen logischen Steuerungs-Thread, der einen Betriebssystem-Thread, eine Betriebssystem-Task oder einen Betriebssystemprozess mit anderen Kapseln gemeinsam nutzen kann.

Probleme

Leider gibt es wie bei vielen Architekturentscheidungen keine einfachen Antworten: Die richtige Lösung erfordert einen sorgfältig durchdachten Ansatz. Kleine Architekturprototypen können verwendet werden, um die Auswirkungen bestimmter Auswahlmöglichkeiten zu untersuchen. Konzentrieren Sie sich bei der Erstellung eines Prototyps für die Prozessarchitektur darauf, die Anzahl der Prozesse hin zu den theoretisch möglichen Maximalwerten für das System zu skalieren. Berücksichtigen Sie die folgenden Punkte:

  • Kann die Anzahl der Prozesse auf den Maximalwert skaliert werden? Wie weit über diesen Maximalwert hinaus kann das System ausgereizt werden? Ist Freiraum für ein potenzielles Wachstum vorhanden?
  • Welche Auswirkungen hat es, wenn einige Prozesse in Lightweight-Threads geändert werden, die in einem gemeinsam genutzten Adressraum arbeiten?
  • Wie entwickeln sich die Antwortzeiten, wenn mehr Prozesse hinzugefügt werden? Hat sich der Umfang der Interprozesskommunikation (IPC, Inter-Process Communication) erhöht? Ist eine erhebliche Verschlechterung zu erkennen?
  • Könnte der Umfang der Interprozesskommunikation durch Kombinieren oder Reorganisieren der Prozesse reduziert werden? Würde eine solche Änderung zu großen monolithischen Prozessen führen, für die ein Lastausgleich nur schwer realisierbar ist?
  • Kann gemeinsam genutzter Speicher verwendet werden, um die Interprozesskommunikation zu reduzieren?
  • Sollen die Zeitressourcen gleichmäßig auf alle Prozesse verteilt werden? Kann die Zeitzuordnung berücksichtigt werden? Hat die Änderung der Planungsprioritäten potenzielle Nachteile?

Interobjektkommunikation

Aktive Objekte können synchron oder asynchron miteinander kommunizieren. Synchrone Kommunikation ist hilfreich, weil sie komplexe Kollaborationen durch strikt kontrollierte Einhaltung der Nachrichtenreihenfolge vereinfachen kann. Während ein aktives Objekt einen Schritt von Anfang bis Ende ausführt, der synchrone Aufrufe anderer aktiver Objekte beinhaltet, können alle parallelen Interaktionen, die von anderen Objekten eingeleitet werden, ignoriert werden, bis die vollständige Nachrichtenfolge abgeschlossen ist.

Dies kann in manchen Fällen, in anderen wiederum problematisch sein, da es passieren kann, dass ein wichtigeres Ereignis mit hoher Priorität warten muss (Prioritätsinversion). Dieses Problem kann sich verschlimmern, wenn das synchron aufgerufene Objekt selbst blockiert ist, weil es auf eine Antwort auf einen eigenen synchronen Aufruf wartet. Dies kann zu unbegrenzter Prioritätsinversion führen. Wenn die Kette der synchronen Aufrufe eine Schleife enthält, kann dies im schlimmsten Fall zu einem Deadlock führen.

Asynchrone Aufrufe umgehen dieses Problem, indem sie unbegrenzte Antwortzeiten zulassen. Je nach Softwarearchitektur führt asynchrone Kommunikation jedoch häufig zu komplexerem Code, da ein aktives Objekt zu einer Zeit möglicherweise auf mehrere asynchrone Ereignisse antworten muss (die wiederum selbst eine komplexe Abfolge asynchroner Interaktionen mit anderen aktiven Objekten nach sich ziehen können). Dies kann schwierig zu implementieren und fehleranfällig sein.  

Die Verwendung asynchroner Nachrichtenübermittlungstechnologie mit zuverlässiger Nachrichtenübertragung kann die Anwendungsprogrammierung vereinfachen. Die Anwendung kann ihre Operationen fortsetzen, selbst wenn die Netzverbindung oder ferne Anwendung nicht verfügbar ist. Asynchrone Nachrichtenübertragung schließt nicht aus, dass sie auch in einem synchronen Modus verwendet werden kann. Synchrone Technologie setzt voraus, dass eine Verbindung verfügbar ist, wenn die Anwendung verfügbar ist. Da bekannt ist, dass eine Verbindung vorhanden ist, kann die Handhabung der COMMIT-Verarbeitung einfacher sein.

In dem in Rational Unified Process für Echtzeitsysteme empfohlenem Ansatz kommunizieren Kapseln asynchron über Signale auf der Basis bestimmter Protokolle. Trotzdem kann eine synchrone Kommunikation erreicht werden, indem ein Signal in jede Richtung gesendet wird (Signalpaare).

Pragmatik

Obwohl der Aufwand für Kontextwechsel für aktive Objekte sehr gering sein kann, ist es möglich, dass einige Anwendungen die Kosten trotzdem für inakzeptabel halten. Dies ist gewöhnlich in Situationen der Fall, in denen große Datenmengen mit hoher Geschwindigkeit verarbeitet werden müssen. In diesen Fällen müssen Sie möglicherweise auf die Verwendung passiver Objekte und eher traditionelle (aber risikoreicheren) Techniken für Parallelitätsverwaltung zurückgreifen, z. B. auf Semaphore.

Dies impliziert jedoch nicht unbedingt, den Ansatz mit aktiven Objekten völlig fallen zu lassen. Selbst in solch datenintensiven Anwendungen macht der leistungskritische Abschnitt einen relativ kleinen Teil des Gesamtsystems aus. Dies bedeutet, dass der Rest des Systems trotzdem die Vorteile des Konzepts aktiver Objekte nutzen kann.

Im Allgemeinen ist die Leistung nur eines der Designkriterien beim Systemdesign. Wenn das System komplex ist, sind Kriterien wie Wartungsfreundlichkeit, einfache Änderungsmöglichkeit, Verständlichkeit usw. genauso wichtig, wenn nicht gar wichtiger. Der Ansatz mit aktiven Objekten hat klare Vorteile, da er einen Großteil der Komplexität von Parallelität und Parallelitätsverwaltung verbirgt, aber gleichzeitig erlaubt, das Design mit anwendungsspezifischen Begriffen zu beschreiben und keine technologiespezifischen Mechanismen auf niedriger Ebene voraussetzt.

Heuristik

Fokus auf Interaktionen zwischen parallelen Komponenten

Parallele Komponenten ohne Interaktionen sind ein nahezu triviales Problem. Fast alle Designherausforderungen haben etwas mit Interaktionen zwischen gleichzeitig ausgeführten Tasks zu tun. Deshalb müssen wir unsere Energie zunächst auf das Verständnis der Interaktionen konzentrieren. Eine der Fragen, die Sie stellen müssen, sind:

  • Ist die Interaktion unidirektional, bidirektional oder mehrdirektional?
  • Gibt es eine Client/Server- oder Master/Slave-Beziehung?
  • Ist eine Form der Synchronisation erforderlich?

Nachdem wir die Interaktionen verstanden haben, können wir darüber nachdenken, wie wir sie implementieren. Die Implementierung muss so gewählt werden, dass sie das einfachste Design ergibt, das den Leistungszielen des Systems entspricht. Zu den Leistungsanforderungen gehören im Allgemeinen der Gesamtdurchsatz und eine akzeptable Latenzzeit beim Antworten auf extern generierte Ereignisse.

Diese Aspekte sind bei Echtzeitproblemen noch viel kritischer, weil Echtzeitsysteme weniger tolerant gegenüber Leistungsschwankungen sind, z. B. gegenüber Abweichungen in der Antwortzeit oder verpassten Terminen.

Externe Schnittstellen isolieren und kapseln

Es ist davon abzuraten, bestimmte Annahmen über externe Schnittstellen in einer Anwendung zu integrieren, und extrem ineffizient, mehrere blockierte Steuerungs-Threads zu haben, die auf ein Ereignis warten. Ordnen Sie stattdessen der jeweiligen Task, die das Ereignis erkennt, ein einzelnes Objekt zu. Wenn das Ereignis eintritt, kann dieses Objekt alle anderen informieren, die über das Ereignis Bescheid wissen müssen. Dieses Design basiert auf dem anerkannten und gewährten Designmuster "Beobachter" [GAM94]. Dieses Muster kann problemlos erweitert werden, um dem "Veröffentlichungskomponente-Subskribent-Muster" eine noch höhere Flexibilität zu geben, in dem ein Veröffentlichungskomponentenobjekt als Zwischenstation zwischen den Ereignisdetektoren und den Objekten auftritt, die an dem Ereignis interessiert sind ("Subskribenten") [BUS96].

Blockierendes und abfragendes Verhalten isolieren

Aktionen in einem System können durch das Auftreten extern generierter Ereignisse ausgelöst werden. Ein sehr wichtiges extern generiertes Ereignis kann einfach das Vergehen von Zeit selbst sein, das durch den Sekundentakt einer Uhr dargestellt wird. Weitere externe Ereignisse stammen von Eingabeeinheiten, die mit externer Hardware verbunden sind, darunter Benutzerschnittstelleneinheiten, Prozesssensoren und Kommunikationsverbindungen zu anderen Systemen. Dies trifft in großem Umfang auf Echtzeitsysteme zu, die typischerweise eine hohe Konnektivität zur Außenwelt haben.

Damit Software ein Ereignis erkennen kann, muss sie entweder in der Zeit, in der sie auf einen Interrupt wartet, blockiert werden oder in regelmäßigen Abständen bei der Hardware anfragen, ob das Ereignis eingetreten ist. Im letzeren Fall muss der Abfragezyklus unter Umständen kurz sein, um kurzlebige Ereignisse oder Mehrfachvorkommen nicht zu verpassen oder um einfach die Latenzzeit zwischen Ereigniseintritt und Ereigniserkennung zu minimieren.

Der interessante Aspekt ist hier, dass auch bei selten eintretenden Ereignissen irgendeine Software blockiert werden muss, die auf das Ereignis wartet oder das Ereignis häufig prüft. Viele (wenn nicht gar die meisten) Ereignisse, die ein System behandeln muss, treten jedoch nur selten ein. Die meiste Zeit passiert in den Systemen nichts von Bedeutung.

Das Aufzugsystem liefert hierfür viele gute Beispiele. Zu den wichtigen Ereignissen im Leben eines Aufzugs gehören das Anfordern eines Aufzugs, die Auswahl des Stockwerks, das Blockieren der Aufzugtür durch einen Passagier und das Fahren von einem Stockwerk zum nächsten. Einige dieser Ereignisse erfordern sehr zeitkritische Antworten, aber alle Ereignisse treten im Vergleich mit der Zeitskala der gewünschten Antwortzeit äußerst selten auf.

Ein einzelnes Ereignis kann viele Aktionen auslösen, und die Aktionen können von den Zuständen verschiedener Objekte abhängen. Außerdem können verschiedene Konfigurationen eine Systems dasselbe Ereignis unterschiedlich verwenden. Wenn ein Aufzug beispielsweise ein Stockwerk passiert, muss die Anzeige in der Aufzugkabine aktualisiert werden, und der Aufzug selbst muss wissen, wo er ist, damit er in der Lage ist, auf eine neue Aufrufanforderung und eine neue Stockwerkauswahl zu reagieren. In jedem Stockwerk kann es Aufzugpositionsanzeigen geben.

Bevorzugung von reaktivem Verhalten gegenüber abfragendem Verhalten

Abfragen (Polling) sind kostenintensiv. Bei Abfragen muss ein Teil des Systems regelmäßig seine Arbeit unterbrechen, um zu prüfen, ob ein Ereignis eingetreten ist. Ist eine schnelle Reaktion auf das Ereignis erforderlich, muss das System in kurzen Zeitabständen prüfen, ob das Ereignis vorliegt, was den Umfang der anderen Arbeiten, die ausgeführt werden könnten, weiter einschränkt.

Es ist wesentlich effizienter, dem Ereignis einen Interrupt zuzuordnen, so dass der ereignisabhängige Code durch den Interrupt aktiviert wird. Obwohl Interrupts manchmal vermieden werden, weil sie als "kostenintensiv" gelten, kann eine durchdachte Verwendung von Interrupts wesentlich effizienter sein als wiederholte Abfragen.

Interrupts als Ereignisbenachrichtigungmechanismen werden in solchen Fällen bevorzugt, in denen der Ereigniseintritt zufällig und selten ist und die meisten Abfragen nur feststellen würden, dass das Ereignis nicht eingetreten ist. Abfragen werden in den Fällen bevorzugt, in denen die Ereignisse in regelmäßigen und vorhersagbaren Intervallen eintreten und in denen die meisten Abfragen feststellen würden, dass das Ereignis eingetreten ist. Dazwischen gibt es Fälle, in denen es egal ist, ob Abfrage- oder reaktives Verhalten verwendet wird, und in denen beide Mechanismen gleichermaßen geeignet sind. Aufgrund der Zufälligkeit von Ereignissen in der realen Welt wird jedoch das reaktive Verhalten meistens bevorzugt.

Bevorzugung von Ereignisbenachrichtigung gegenüber Rundsenden von Daten

Das Rundsenden von Daten (normalerweise mit Signalen) ist kostenintensiv und in gewöhnlich verschwenderisch. Möglicherweise sind nur wenige Objekte an den Daten interessiert, aber jedes (oder viele) muss seine Arbeit unterbrechen, um die Daten zu überprüfen. Ein besserer, weniger ressourcenintensiver Ansatz ist die Verwendung von Benachrichtigungen, so dass nur die Objekte informiert werden, die sich dafür interessieren, dass das Ereignis eingetreten ist. Beschränken Sie das Rundsenden auf Ereignisse, die die Aufmerksamkeit vieler Objekte erfordern (gewöhnlich Zeit- oder Synchronisationsereignisse).

Lightweight-Mechanismen häufig und Heavyweight-Mechanismen sparsam verwenden

Dies bedeutet im Einzelnen:

  • Verwenden Sie passive Objekte und synchrone Methodenaufrufe, wenn Parallelität kein Thema ist, sondern unverzügliche Antwort.
  • Verwenden Sie aktive Objekte und asynchrone Nachrichten für die große Mehrzahl von Parallelitätskonzepten auf Anwendungsebene.
  • Verwenden Sie Betriebssystem-Threads, um blockierende Elemente zu isolieren. Ein aktives Objekt kann einem Betriebssystem-Thread zugeordnet werden.
  • Verwenden Sie Betriebssystemprozesse für maximale Isolation. Es sind separate Prozesse erforderlich, wenn Programme unabhängig voneinander gestartet und beendet werden müssen, und für Subsysteme, die verteilt werden müssen.
  • Verwenden Sie separate CPUs für die physische Verteilung oder für die reine Leistung.

Die vielleicht wichtigste Richtlinie für die Entwicklung effizienter paralleler Anwendungen ist eine maximale Verwendung von Lightweight-Parallelitätsmechanismen. Hardware und Betriebssystemsoftware spielen eine wichtige Rolle bei der Unterstützung von Parallelität, aber beide bieten eher Heavyweight-Mechanismen an, die dem Anwendungsdesigner einen Großteil der Arbeit überlassen. Wir müssen eine große Lücke zwischen den verfügbaren Tools und den Bedürfnissen paralleler Anwendungen schließen.

Die beiden Schlüssel-Features aktiver Objekte helfen, diese Lücke zu schließen:

  • Sie vereinheitlichen die Designabstraktionen, indem Sie die Basiseinheit der Parallelität (einen Steuerungs-Thread) kapseln, der mit einem der vom Betriebssystem oder von der CPU bereitgestellten Mechanismen implementiert werden kann.
  • Wenn aktive Objekte einen Betriebssystem-Thread gemeinsam nutzen, werden sie zu einem sehr effizienten, schlanken Parallelitätsmechanismus, der ansonsten direkt in der Anwendung implementiert werden müsste.

Aktive Objekte können auch eine ideale Umgebung für die passiven Objekte sein, die von Programmiersprachen bereitgestellt werden. Wenn Sie ein System vollständig auf der Grundlage paralleler Objekte ohne prozedurorientierte Artefakte wie Programme und Prozesse entwerfen, erhalten Sie ein modulares, kohäsives und verständliches Design.

Leistungsbigotterie vermeiden

In den meisten Systemen verwendet weniger als 10 % des Codes mehr als 90 % der CPU-Zyklen.

Viele Systemdesigner meinen, es müsste jede Codezeile optimiert werden. Stattdessen sollten Sie lieber die 10 % des Codes optimieren, der am häufigsten und am längsten ausgeführt wird. Entwerfen Sie die anderen 90 % mit Schwerpunkt auf Verständlichkeit, Wartungsfreundlichkeit, Modularität und einfache Implementierung.

Mechanismen auswählen

Die nicht funktionalen Anforderungen und die Architektur des Systems wirken sich auf die Auswahl der Mechanismen aus, die für die Implementierung ferner Prozeduraufrufe verwendet werden. Eine Übersicht über die Arten von Abwägungen zwischen den Alternativen finden Sie im Folgenden:  

Mechanismus Verwendung Kommentare
Nachrichtenübertragung Asynchroner Zugriff auf Enterprise-Server Middleware für Nachrichtenübertragung kann die Anwendungsprogrammierung erheblich vereinfachen, indem sie die Steuerung der Warteschlange, Zeitlimitüberschreitungen und Wiederherstellungs-/Neustartbedingungen behandelt. Sie können Middleware für Nachrichtenübertragung auch in einem pseudosynchronen Modus verwenden. Messaging-Technologie kann gewöhnlich große Nachrichtenlängen unterstützen. Einige RPC-Ansätze sind bezüglich der Nachrichtenlänge möglicherweise eingeschränkt und erfordern zusätzliche Programmierarbeiten für die Behandlung großer Nachrichten.
JDBC/ODBC Datenbankaufrufe Dies sind datenbankunabhängige Schnittstellen, damit Java-Servlets oder -Anwendungsprogramme Aufrufe an Datenbanken absetzen können, die sich auf demselben oder einem anderen Server befinden.
Native Schnittstellen Datenbankaufrufe Viele Datenbanklieferanten haben native Anwendungsprogrammschnittstellen zu ihren eigenen Datenbanken implementiert, die im Vergleich mit ODBC einen Leistungsvorteil bieten, was jedoch zu Lasten der Portierbarkeit der Anwendung geht.
Fernprozeduraufruf Zum Aufruf von Programmen auf fernen Servern Möglicherweise müssen Sie nicht auf RPC-Ebene programmieren, wenn Sie ein Anwendungserstellungsprogramm haben, das dies für Sie übernimmt.
Interaktiv In e-business-Anwendungen selten verwendet Typischerweise Kommunikation zwischen Programmen auf niedriger Ebene über Protokolle wie APPC oder Sockets.

Zusammenfassung

Viele Systeme erfordern paralleles Verhalten und verteilte Komponenten. Die meisten Programmiersprachen geben uns wenig Hilfestellung zu diesen Problemen. Die Erfahrung hat gezeigt, dass gute Abstraktionen erforderlich sind, um den Bedarf an Parallelität in Anwendungen und die Optionen für die Implementierung von Parallelität in Software zu verstehen. Wir haben auch festgestellt, dass parallele Software tendenziell zwar komplexer als nicht parallele Software ist, aber trotzdem in der Lage ist, das Design von Systemen, die mit Parallelität in der realen Welt umgehen müssen, erheblich zu vereinfachen.