Aufgabe: Laufzeitarchitektur beschreiben
Diese Aufgabe definiert für das System eine Prozessarchitektur, die sich auf die aktiven Klassen und ihre Instanzen sowie deren Beziehung zu Betriebssystem-Threads und -prozessen konzentriert.
Disziplinen: Analyse und Design
Zweck
  • Anforderungen für Parallelität analysieren
  • Prozesse und deren Lebenszyklen identifizieren 
  • Mechanismen für Interprozesskommunikation identifizieren und Ressourcen für Interprozesskoordination zuordnen 
  • Modellelemente auf Prozesse verteilen.
Beziehungen
RollenPrimärer Ausführender: Zusätzliche Ausführende:
EingabenVerbindlich: Optional:
Ausgaben
Prozessverwendung
Hauptbeschreibung

Aktive Objekte (d. h. Instanzen aktiver Klassen) werden verwendet, um parallele Ausführungs-Threads im System darzustellen: Theoretisch hat jedes aktive Objekt seinen eigenen Steuer-Thread und ist normalerweise das Stammelement eines Ausführungs-Stack-Frame. Die Zuordnung aktiver Objekte zu aktuellen Betriebssystem-Threads oder -prozessen kann je nach Anforderungen für die Reaktionsfähigkeit variieren und wird auch von Überlegungen zu dem für den Kontextwechsel erforderlichen Systemaufwand beeinflusst. Beispielsweise ist es für eine Reihe aktiver Objekte möglich, in Kombination mit einem einfachen Scheduler einen einzelnen Betriebssystem-Thread gemeinsam zu nutzen, so dass der Eindruck der Parallelität entsteht. Wenn jedoch eines der aktiven Objekte blockiert wird, z. B. durch synchrone Ein- und Ausgabe, sind andere aktive Objekte in der Gruppe nicht in der Lage, auf Ereignisse zu reagieren, die eintreten, während der Betriebssystem-Thread blockiert ist.

Entscheidet man sich für das andere Extrem, nämlich jedem aktiven Objekt einen eigenen Betriebssystem-Thread zuzuordnen, erzielt man eine längere Reaktionszeit, vorausgesetzt, die Verarbeitungsressourcen werden durch den zusätzlichen Systemaufwand für den Kontextwechsel nicht nachteilig beeinflusst.

In Echtzeitsystemen wird das Arbeitsergebnis: Kapsel zur Modellierung der Parallelität empfohlen. Wie aktive Klassen hat jede Kapsel ihren eigenen theoretischen Steuer-Thread, Kapseln besitzen jedoch eine zusätzliche Kapselungs- und Kompositionssemantik, um die Modellierung komplexer Echtzeitprobleme zu vereinfachen.

Diese Aufgabe definiert für das System eine Prozessarchitektur, die sich auf die aktiven Klassen und ihre Instanzen sowie deren Beziehung zu Betriebssystem-Threads und -prozessen konzentriert. Analog dazu wird die Prozessarchitektur bei Echtzeitsystemen mit Kapseln und deren Zuordnung zu Betriebssystemprozessen und -Threads definiert.

In der frühen Ausarbeitungsphase ist diese Architektur recht provisorisch, aber im späten Stadium sollten die Prozesse und Threads klar definiert sein. Die Ergebnisse dieser Aufgabe werden im Designmodell erfasst, insbesondere in der Prozesssicht (siehe Konzept: Prozesssicht).

Schritte
Anforderungen für Parallelität analysieren
Zweck:  Es ist erforderlich, das Ausmaß der parallelen Ausführung für das System zu definieren. Diese Definition hilft beim Formen der Architektur. 

Bei der Aufgabe: Designelemente identifizieren wurde der Bedarf an Anforderungen für die Parallelität in der Aufgabendomäne, der sich ganz natürlich ergibt, berücksichtigt.  

Das Ergebnis war eine Gruppe aktiver Klassen, die logische Steuer-Threads im System repräsentiert. In Echtzeitsystemen werden diese Klassen durch das Arbeitsergebnis: Kapsel repräsentiert.

In diesem Schritt werden andere Quellen für die Anforderungen für Parallelität berücksichtigt, und zwar diejenigen, die durch die nicht funktionalen Anforderungen des Systems vorgegeben werden.

Anforderungen für Parallelität werden durch folgende Faktoren bestimmt:

  • Verteilungsgrad des Systems. Ein System, dessen Verhalten virtuell auf verschiedene Prozessoren oder Knoten verteilt werden muss, erfordert eine Mehrprozessarchitektur. Ein System, das eine Art Datenbankmanagementsystem oder einen Transaktionsmanager verwendet, muss auch die Prozesse berücksichtigen, die von diesen wichtigen Subsystemen eingeführt werden.
  • Rechenintensität der Schlüsselalgorithmen. Um gute Reaktionszeiten zu erreichen, müssen rechenintensive Aktivitäten möglicherweise in einem eigenen Prozess oder Thread untergebracht werden, damit das System noch auf Benutzereingaben reagieren kann, während die Verarbeitung, wenn auch mit weniger Ressourcen, ausgeführt wird.
  • Grad der parallelen Verarbeitung, der von der Umgebung unterstützt wird. Wenn das Betriebssystem oder die Betriebsumgebung keine Threads (Lightweight-Prozesse) unterstützt, macht es wenig Sinn, über deren Auswirkungen auf die Systemarchitektur nachzudenken.
  • Bedarf an Fehlertoleranz im System. Sicherungsprozessoren erfordern einen Sicherungsprozess, und Primär- und Sicherungsprozesse müssen synchronisiert werden.
  • Eingangsmuster der Ereignisse im System. In Systemen mit externen Einheiten oder Sensoren können die Eingangsmuster eingehender Ereignisse sich je nach Sensor unterscheiden. Einige Ereignisse können regelmäßig (d. h., in einem festen Intervall, mit einer geringfügigen Abweichung nach oben oder unten) oder unregelmäßig (d. h. in verschiedenen Intervallen) eintreten. Aktive Klassen, die Einheiten repräsentieren, die verschiedene Ereignismuster generieren, werden normalerweise verschiedenen Betriebssystem-Threads mit verschiedenen Zeitplanalgorithmen zugeordnet, um sicherzustellen, dass Ereignisse oder Endtermine für die Verarbeitung nicht verpasst werden (wenn dies eine Systemanforderung ist). Diese Überlegung gilt in gleicher Weise für Kapseln, wenn sie im Design von Echtzeitsystemen verwendet werden.

Wie bei Architekturproblemen können diese Anforderungen so beschaffen sein, dass sie sich gegenseitig ausschließen. Es ist nicht unüblich, zumindest zu Beginn Anforderungen bewältigen zu müssen, die miteinander im Konflikt stehen. Das Ordnen der Anforderungen nach Wichtigkeit hilft bei der Lösung des Konflikts.

Prozesse und Threads identifizieren
Zweck:  Die Prozesse und Threads für das System definieren.  

Die einfachste Methode ist, alle aktiven Objekte einem gemeinsamen Thread oder Prozess zuzuordnen und einen einfachen aktiven Objekt-Scheduler zu verwenden, da dies den Systemaufwand für den Kontextwechsel reduziert. In einigen Fällen kann es jedoch erforderlich sein, die aktiven Objekte auf einen oder mehrere Threads oder Prozesse zu verteilen. Das ist so gut wie sicher bei den meisten Echtzeitsystemen der Fall, in denen die Kapseln, die verwendet werden, um die logischen Threads zu repräsentieren, feste Terminvorgaben einhalten müssen.

Wenn ein aktives Objekt, das einen Betriebssystem-Thread mit anderen aktiven Objekten gemeinsam nutzt, einen synchronen Aufruf an einen anderen Prozess oder Thread absetzt und dieser Aufruf den gemeinsam genutzten Betriebssystem-Thread des aufrufenden Objekts blockiert, werden alle anderen aktiven Objekte, die sich im aufrufenden Prozess befinden, automatisch ausgesetzt. Allerdings muss das nicht so sein: Ein Aufruf, der vom Standpunkt des aktiven Objekts betrachtet synchron ist, kann, betrachtet vom Standpunkt des einfachen Schedulers, der die Gruppe aktiver Objekte steuert, asynchron bearbeitet werden. Der Scheduler setzt das aktive Objekt, das den Aufruf absetzt, aus, wartet auf den Abschluss seines synchronen Aufrufs und plant dann die Ausführung anderer aktiver Objekte.  

Wenn die ursprüngliche "synchrone" Operation abgeschlossen ist, kann das aufrufende aktive Objekt fortgesetzt werden. Dieser Ansatz ist möglicherweise nicht immer möglich, da der Scheduler möglicherweise nicht so entworfen werden kann, dass er alle synchronen Aufrufe abfängt, bevor sie blockiert werden. Beachten Sie, dass ein synchroner Aufruf zwischen aktiven Objekten vom Scheduler mit demselben Betriebssystemprozess oder -Thread auf diese Weise gehandhabt werden kann und, vom Standpunkt des aufrufenden aktiven Objekts betrachtet, einem Prozeduraufruf tatsächlich gleichwertig ist.

Dies führt zu dem Schluss, dass aktive Objekte in Prozessen oder Threads zusammengeführt werden müssen, wenn sie parallel zu synchronen Aufrufen, die den Thread blockieren, ausgeführt werden sollen. Das bedeutet Folgendes: Ein aktives Objekt wird mit einem anderen Objekt, das synchrone, den Thread blockierende Aufrufe verwendet, nur in einen Prozess oder Thread gestellt, wenn es nicht parallel zu diesem Objekt ausgeführt werden muss und warten kann, wenn das andere Objekt blockiert wird. In dem Extremfall, dass die Reaktionsfähigkeit kritisch ist, kann dies dazu führen, dass ein separater Thread oder Prozess für jedes aktive Objekt benötigt wird.

Für Echtzeitsysteme bedeuten die nachrichtenbasierten Schnittstellen der Kapseln, dass es einfacher ist, sich einen Scheduler vorzustellen, der sicherstellt, dass die unterstützenden Betriebssystem-Threads - zumindest für die Kommunikation zwischen den Kapseln - nie blockiert werden, selbst wenn eine Kapsel mit einer anderen Kapsel synchron kommuniziert. Eine Kapsel kann jedoch immer noch eine Anforderung direkt an das Betriebssystem absetzen, z. B. für einen synchronen zeitverzögerten Wartestatus, der den Thread blockieren würde. Es müssen Konventionen für von Kapseln aufgerufene Services auf niedriger Ebene festgelegt werden, die dieses Verhalten verhindern, wenn die Kapseln einen Thread gemeinsam nutzen (und einen einfachen Scheduler zur Simulierung der Parallelität verwenden) sollen.

Allgemein gilt, dass es in den oben genannten Situationen besser ist, Lightweight-Threads anstelle von eigenständigen Prozessen zu verwenden, da dies weniger Systemaufwand erfordert. Sie können immer noch bestimmte Prozessmerkmale in bestimmten Sonderfällen nutzen. Da Threads denselben Adressraum gemeinsam nutzen, sind sie von Natur aus riskanter als Prozesse. Sollen zufällige Überschreibungen vermieden werden, sind Prozesse zu bevorzugen. Da Prozesse außerdem in den meisten Betriebssystemen unabhängige Arbeitseinheiten mit Wiederherstellung sind, kann es nützlich sein, aktive Objekte Prozessen zuzuordnen, wenn sie unabhängig voneinander wiederhergestellt werden sollen. Das bedeutet, dass alle aktiven Objekte, die als Einheit wiederhergestellt werden müssen, in denselben Prozess gestellt werden können.

Erstellen Sie für jeden separaten Steuerungsablauf, der vom System benötigt wird, einen Prozess oder Thread (Lightweight-Prozess). Threads sollten verwendet werden, wenn ein verschachtelter Steuerungsablauf erforderlich ist (d. h., wenn in einem Prozess ein unabhängiger Steuerungsablauf auf der Ebene der untergeordneten Aufgaben benötigt wird).

Beispielsweise können eigenständige Steuer-Threads erforderlich sein, um die folgenden Aktionen auszuführen:

  • Probleme auf verschiedene Softwarebereiche verteilen
  • Mehrere CPUs in einem oder mehreren Knoten in einem verteilten System nutzen
  • CPU-Auslastung durch Zuordnung von Zyklen zu anderen Aktivitäten bei ausgesetztem Steuer-Thread erhöhen
  • Prioritäten für Aktivitäten vergeben
  • Lastverteilung auf mehrere Prozesse und Prozessoren unterstützen
  • Höhere Systemverfügbarkeit durch Sicherungsprozesse herstellen
  • DBMS, Transaktionsmanager oder andere wichtige Subsysteme.

Beispiel

Beim Geldautomaten (GA) müssen asynchrone Ereignisse von drei verschiedenen Quellen gehandhabt werden: dem Benutzer des Systems, den GA-Einheiten (z. B. bei einem Stau in der Geldausgabe) und dem GA-Netz (bei einer Beendigungsanweisung durch das Netz). Zur Handhabung dieser asynchronen Ereignisse können Sie drei eigenständige Ausführungs-Threads im Geldautomaten mit aktiven UML-Klassen selbst definieren, wie unten dargestellt.

Darstellung von GA-Prozessen und -Threads

Prozesse und Threads im Geldautomaten

Prozesslebenszyklen identifizieren
Zweck:  Festlegen, wann Prozesse und Threads erstellt und gelöscht werden sollen.  

Jeder Prozess oder Steuer-Thread muss erstellt und zerstört werden. In einer Architektur mit einem einzelnen Prozess findet die Erstellung des Prozesses statt, wenn die Anwendung gestartet wird, und das Löschen des Prozesses findet statt, wenn die Anwendung beendet wird. In Architekturen mit mehreren Prozessen werden neue Prozesse (oder Threads) normalerweise gestartet oder durch das Betriebssystem vom Anfangsprozess abgespalten, wenn die Anwendung gestartet wird. Auch diese Prozesse müssen explizit gelöscht werden.

Die Ereignisfolge, die zum Erstellen und Löschen von Prozessen verwendet wird, muss, wie der Mechanismus für das Erstellen und Löschen, festgelegt und dokumentiert werden.

Beispiel

Im Geldautomaten wird ein Hauptprozess gestartet, der für die Koordination des gesamten Systemverhaltens zuständig ist. Dieser Hauptprozess startet eine Reihe untergeordneter Steuer-Threads, um verschiedene Teile des Systems zu überwachen: die Einheiten im System und die vom Kunden und vom GA-Netz ausgehenden Ereignisse. Die Erstellung dieser Prozesse und Threads kann mit aktiven Klassen in UML, die Erstellung von Instanzen dieser aktiven Klassen in einem Ablaufdiagramm dargestellt werden, wie unten zu sehen ist:

Darstellung des Systemstarts und der Thread-Erstellung

Erstellung von Prozessen und Threads während der Systeminitialisierung

Mechanismen für Interprozesskommunikation identifizieren
Zweck:  Die Kommunikationswege für Prozesse und Threads identifizieren.  

Mechanismen für Interprozesskommunikation bieten die Möglichkeit, Nachrichten zwischen Objekten, die in separaten Prozessen ausgeführt werden, zu senden.

Typische Mechanismen für Interprozesskommunikation sind folgende:

  • Gemeinsam genutzter Speicher mit oder ohne Semaphoren zur Sicherstellung der Synchronisation
  • Rendezvous, insbesondere bei Unterstützung durch eine Sprache, wie z. B. Ada
  • Semaphore, mit denen simultaner Zugriff auf gemeinsam genutzte Ressourcen blockiert wird
  • Nachrichtenübergabe, Punkt-zu-Punkt und Punkt-zu-Multipunkt
  • Mailboxen
  • RPC - Fernprozeduraufruf
  • Ereignis-Broadcasting - Verwendung eines "Softwarebusses" (Nachrichtenbusarchitektur).

Die Auswahl des Mechanismus für Interprozesskommunikation ändert die Systemmodellierung. Beispielsweise sind in einer Nachrichtenbusarchitektur keine expliziten Zuordnungen zwischen Objekten erforderlich, um Nachrichten senden zu können.

Ressourcen für Interprozesskoordination zuordnen
Zweck: Knappe Ressourcen zuordnen
Potenzielle Leistungsengpässe vorab erkennen und steuern 

Mechanismen für Interprozesskommunikation sind normalerweise knapp. Semaphore, gemeinsam genutzter Speicher und Mailboxen haben normalerweise eine feste Größe und Zahl und können nicht ohne erheblichen Kostenaufwand erweitert werden. RPC, Nachrichten und Ereignis-Broadcasting benötigen immer knapper werdende Netzbandbreite. Überschreitet das System einen Schwellenwert für Ressourcen, erfährt es normalerweise einen nichtlinearen Leistungsabfall: Wenn eine knappe Ressource aufgebraucht ist und dennoch weiter angefordert wird, hat das unangenehme Auswirkungen.

Wenn knappe Ressourcen nicht verfügbar sind, sind verschiedene Strategien möglich:

  • Bedarf nach knappen Ressourcen durch Verringerung der Prozessanzahl reduzieren
  • Nutzung knapper Ressourcen ändern (für einen oder mehrere Prozesse eine andere, weniger knappe Ressource für den Mechanismus für Interprozesskommunikation auswählen)
  • Die Menge der knappen Ressourcen erhöhen (z. B. die Anzahl der Semaphore erhöhen). Das ist für relativ kleine Änderungen möglich, es gibt jedoch häufig Nebenwirkungen oder feste Grenzwerte.
  • Knappe Ressource gemeinsam nutzen (z. B. die Ressource nur bei Bedarf zuordnen und nach Nutzung wieder freigeben). Das ist eine aufwändige Vorgehensweise, mit der die Ressourcenkrise vermieden werden kann.

Unabhängig von der ausgewählten Strategie ist es besser, dass das System in seiner Leistung allmählich schwächer wird, als dass es abstürzt; es sollte passende Rückmeldungen an einen Systemadministrator abgeben, damit das Problem vor Ort gelöst werden kann (falls möglich), sobald das System-Deployment abgeschlossen ist.

Wenn das System eine besondere Konfiguration der Laufzeitumgebung erfordert, um die Verfügbarkeit einer kritischen Ressource (häufig durch Rekonfiguration des Betriebssystem-Kernel gesteuert) zu verbessern, muss diese Konfiguration entweder automatisch über die Systeminstallation erfolgen, oder Sie müssen einen Systemadministrator beauftragen, dies zu tun, bevor das System in Betrieb genommen wird. Möglicherweise muss das System erneut gestartet werden, damit die Änderung wirksam wird.

Prozesse in der Implementierungsumgebung zuordnen
Zweck:  Die Steuerungsabläufe den von der Implementierungsumgebung unterstützten Konzepten zuordnen.  

Konzeptionelle Prozesse müssen spezifischen Konstrukten in der Betriebsumgebung zugeordnet werden. In vielen Umgebungen gibt es verschiedene Optionen für Prozesstypen, normalerweise mindestens Prozesse und Threads. Die Optionen richten sich nach dem Grad der Koppelung (Prozesse sind eigenständig, wohingegen Threads im Kontext eines übergeordneten Prozesses ausgeführt werden) und den Leistungsanforderungen des Systems (die Interprozesskommunikation zwischen Threads ist allgemein schneller und effizienter als die zwischen Prozessen).

In vielen Systemen gibt es eine maximale Anzahl von Threads per Prozess oder von Prozessen per Knoten. Diese Grenzwerte sind vielleicht nicht absolut, können jedoch durch praktische Gegebenheiten, nämlich die Verfügbarkeit knapper Ressourcen, vorgegeben sein. Die Threads und Prozesse, die bereits auf einem Zielknoten aktiv sind, müssen parallel zu den in der Prozessarchitektur vorgeschlagenen Threads und Prozessen berücksichtigt werden. Die Ergebnisse des früheren Schritts Ressourcen für Interprozesskoordination zuordnen müssen berücksichtigt werden, wenn die Zuordnung vorgenommen wird, um sicherzustellen, dass kein neues Leistungsproblem entsteht.

Designelemente zu Steuer-Threads zuordnen
Zweck:  Festlegen, in welchen Steuer-Threads Klassen und Subsysteme ausgeführt werden sollen.  

Instanzen einer bestimmten Klasse oder eines bestimmten Subsystems müssen in mindestens einem Steuer-Thread, der die Ausführungsumgebung für die Klasse oder das Subsystem bereitstellt, ausgeführt werden. Tatsächlich können sie in verschiedenen Prozessen ausgeführt werden.

Bei simultaner Verwendung zweier verschiedener Strategien wird der passende Umfang der Parallelität bestimmt und die passende Prozessgruppe definiert:

Von innen nach außen

  1. Stellen Sie, ausgehend vom Designmodell, Klassen und Subsysteme in Gruppen von Elementen zusammen, die eng miteinander kooperieren und im selben Steuer-Thread ausgeführt werden müssen. Berücksichtigen Sie die Auswirkungen, die die Einführung der Interprozesskommunikation in der Mitte einer Nachrichtenfolge hat, bevor Sie einzelne Elemente in eigenständige Steuer-Threads stellen.
  2. Umgekehrt sollten Sie Klassen und Subsysteme, die in keinster Weise interagieren, trennen, indem Sie sie in eigenständige Steuer-Threads stellen.
  3. Dieses Clustering wird fortgesetzt, bis die Anzahl der Prozesse auf die kleinste Anzahl reduziert wurde, die noch eine Verteilung und Verwendung der physischen Ressourcen erlaubt.

Von außen nach innen

  1. Definieren Sie externe Stimuli, auf die das System reagieren muss. Definieren Sie für jeden Stimulus und jeden bereitzustellenden Service einen eigenständigen Steuer-Thread.
  2. Berücksichtigen Sie die Datenintegrität und die Vorgaben für die Serialisierung, um die erste Gruppe von Steuer-Threads auf eine Anzahl zu reduzieren, die von der Ausführungsumgebung unterstützt werden kann.

Dies ist kein linearer deterministischer Prozess, der zu einer optimalen Prozesssicht führt. Es sind einige Iterationen erforderlich, um einen akzeptablen Kompromiss zu erzielen.

Beispiel

Das folgende Diagramm veranschaulicht, wie Klassen im Geldautomaten auf die Prozesse und Threads im System verteilt werden.

Darstellung der Verteilung von GA-Klassen auf Prozesse und Threads

Zuordnung von Klassen zu GA-Prozessen



Weitere Informationen