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
Rollen | Primärer Ausführender:
| Zusätzliche Ausführende:
|
Eingaben | Verbindlich:
| 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.
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:
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
-
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.
-
Umgekehrt sollten Sie Klassen und Subsysteme, die in keinster Weise interagieren, trennen, indem Sie sie in
eigenständige Steuer-Threads stellen.
-
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
-
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.
-
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.
Zuordnung von Klassen zu GA-Prozessen
|
|
Weitere Informationen
Konzepte |
|
Richtlinien |
|
Toolmentoren |
|
© Copyright IBM Corp. 1987, 2006. Alle Rechte vorbehalten.
|
|