Zuerst wird das Actor-Modell behandelt. Hierbei wird auf die Grundlagen, die einzelnen Operationen und zur Vertiefung auf Möglichkeiten der bildlichen Darstellung eingegangen. Den Abschluß bildet schließlich ein einfaches Beispiel.
Es folgt eine Betrachtung des weitergehenden ActorSpace-Modells mit einer grundlegenden Behandlung der neuen Konzepte, die durch neue und erweiterte Operationen ermöglicht werden.
Aktoren sind vollständig gekapselt, d.h. ihr interner Zustand ist von außen weder direkt sichtbar noch manipulierbar. Die einzige Möglichkeit, einen bestehenden Aktor zu beeinflussen, besteht darin, ihm eine Nachricht zu senden, worauf der Empfänger dann mit verschiedenen Aktionen reagieren kann. Dazu besitzt jedes Objekt eine eindeutige, statische Mailadresse mit zugehöriger Mailqueue sowie ein Verhalten (behaviour), das seinen inneren Zustand darstellt. (Wie später deutlich wird, kann ein einzelnes Objekt in Form mehrerer simultan existierender actor machines verschiedene Verhalten aufweisen).
Die Adresse war dem Aktor schon vor Abarbeitung der aktuellen Nachricht bekannt, ist also ein Teil seines aktuellen inneren Zustands.
Die Adresse wurde dem Aktor soeben als ein Teil der Nachricht mitgeteilt.
Der Sender hat den Adressaten soeben selbst mittels create erzeugt und somit dessen Adresse erhalten.
Zu beachten ist, daß der Nachrichtenversand asynchron ist, der Sender also weder auf die Empfangsbereitschaft des Empfängers noch auf die Abarbeitung der Nachricht warten muß. Statt dessen wird vom Mailsystem garantiert, daß die abgeschickte Nachricht in die Mailqueue des Adressaten eingefügt wird. Hingegen wird über die Reihenfolge des Eintreffens keinerlei Aussage gemacht, so daß es möglich wäre, daß eine "später" abgeschickte Nachricht in der Mailqueue weiter vorne eingefügt wird. Daß hier keine Einschränkung getroffen wird, kann sich u.a. positiv auf die Effizienz des Nachrichtenversands auswirken (man denke beispielsweise an die Möglichkeit des adaptiven Routings, die es Nachrichten erlaubt, unterschiedliche Pfade zu benutzen). Ist eine Erhaltung der Reihenfolge gewünscht, kann dies explizit auf einer höheren Ebene ebenso erzwungen werden wie ein vollständig synchroner Versand, da beides nur einen Spezialfall der asynchronen Kommunikation darstellt.
Zu beachten ist hierbei, daß lediglich das Verhalten für die Abarbeitung der nächsten Nachricht bestimmt wird, und das Verhalten, das die weitere Reaktion auf die aktuelle Nachricht definiert, nicht geändert wird. Dies ist ein wichtiger Aspekt zur Erhöhung der möglichen Parallelität und wird später in Zusammenhang mit actor machines noch einmal genauer angesprochen.
Bei anderen Objektmodellen besteht der innere Zustand aus den Werten objektlokaler Variablen. Ein Zustandsübergang geschieht dann durch Zuweisung neuer Werte an die Variablen. Da dies sequentiell geschieht, können inkonsistente Zwischenzustände entstehen. Außerdem gibt es Probleme beim gleichzeitigen Zugriff auf deise Variablen, wenn das Objekt zwei Nachrichten parallel abarbeiten soll. Durch die Verwendung eines monolithischen Verhaltens als innerer Zustand werden solche Probeme im Actor-Modell vermieden.
Ein rein funktionaler Aktor weist ein statisches Verhalten auf und ändert sein Verhalten nicht über eine become-Operation.
Das aufrufende Objekt erhält als Ergebnis von create die identifizierende Mail-Adresse des neuen Aktors, die dann in weiteren Operationen sofort benutzt werden kann (beispielsweise als Ziel einer Nachricht).
Die Vorgänge, die in einem System von Aktoren ablaufen, kann man zur Verdeutlichung auch graphisch darstellen. Im Folgenden werden die oben aufgeführten Operationen anhand zweier solcher Darstellungen näher erläutert.
ist eine abstrakte Darstellung eines Aktors. Dieser besteht aus einer beliebig großen Mail-Queue, deren Slots mit natürlichen Zahlen numeriert werden. Der Aktor X hat seine ersten n-1 Nachrichten schon abgearbeitet und bearbeitet momentan die Nachricht n. Die Operationen werden von einer sogenannten actor machine Xn ausgeführt. Eine solche actor machine besitzt einen inneren Zustand in Form eines Verhaltens, das die auszuführenden Primitiven bestimmt. Im Beispiel versendet Xn eine Nachricht an ein nicht eingezeichnetes Objekt, erzeugt einen neuen Aktor Y und legt damit gleichzeitig dessen initiale actor machine Y1 fest. Weiterhin führt die become-Operation zum Festlegen des replacement behaviour und damit zur Erzeugung der actor machine Xn+1.
Abbildung : abstrakte Darstellung eines Aktors
Parallelität zwischen Sender und Empfänger:
Aufgrund der
asynchronen Kommunikation kann der Sender einer Nachricht sofort weitere
Operationen ausführen, auch wenn der Empfänger die Nachricht noch
nicht vollständig bearbeitet hat oder sogar noch mit anderen Nachrichten
beschäftigt ist.
Parallelität der einzelnen Operationen innerhalb einer actor
machine:
Da die Operationen, die eine actor machine
ausführt, im allgemeinen unabhängig voneinander sind (Ausnahme:
die Mail-Adresse, die eine create-Operation liefert, muß bekannt
sein, bevor diese in weiteren Primitiven verwendet werden kann), können
sie gleichzeitig ausgeführt werden. In wären dies das
Verschicken einer Nachricht, das Erzeugen von Y und die Bestimmung des
Folgeverhaltens.
Parallelität der einzelnen actor machines:
Mehrere
actor machines des gleichen Objekts können parallel arbeiten. Dies
ist möglich, da sie eigene, unabhängige innere Zustände
besitzen. D.h. im Beispiel kann Xn+1 arbeiten, sobald Xn den Folgezustand
festgelegt hat und die Kommunikation n+1 eingetroffen ist (diese Nachricht kann
natürlich schon längere Zeit in der Mail-Queue stehen), auch wenn Xn
noch beschäftigt ist.
Abbildung : event diagram
Auffallend ist noch, daß die Lebenslinien kein definiertes Ende haben. Dies kommt daher, daß es keine explizite Möglichkeit zum Zerstören bestehender Aktoren gibt. Da das Löschen von nicht erreichbaren Objekten auf konzeptioneller Ebene keinerlei Bedeutung hat (bei realen Implementierungen aber durchaus Auswirkungen auf die Performance des Systems hat), wird dies der jeweiligen Implementierung überlassen. Dort geschieht dies mit dem üblichen Verfahren der garbage collection. Dabei wird ausgenutzt, daß ein Objekt, dessen Adresse keinem anderen Objekt mehr bekannt ist und das keine zur Zeit aktiven actor machines besitzt, keine Nachrichten mehr empfangen und somit auch keine Aktionen mehr ausführen kann. Daher kann es in einem solchen Fall aus dem System entfernt werden.
Das komplette System besteht aus 3 Verhaltensbeschreibungen: der eigentlichen Fakultätsberechnung (Fakultät), einem Hilfsobjekt (Customer) und dem aufrufenden Client. Dieser Client erzeugt ein neues Fakultäts-Objekt und sendet diesem anschließend die Nachricht, 3! zu berechnen und das Ergebnis an stdout zu schicken (das Objekt stdout soll hier einfach sämtliche Nachrichten auf den Bildschirm ausgeben).
Fakultät with acquaintances self let communication be an integer n and an actor u become Fakultät(self) if n=0 then send [1] to u else let c=create Customer with acq. n and u {send [n-1, c] to self} Customer with acq. an integer n and an actor u let communication be an integer k {send [n*k] to u } Client with acquaintances stdout let f=create Fakultät with acquaintances f {send [3,stdout] to f}
Zu diesem Zweck dient ein sogenannter customer, der hier als Parameter bei seiner Erzeugung einen Integer n und ein Objekt u erhält. Dieser Aktor macht nun nichts anderes, als eine ihm übermittelte ganze Zahl mit n zu multiplizieren und das Ergebnis an u weiterzuleiten.
Die Fakultäts-Berechnung erzeugt nun einen solchen customer und schickt sich anschließend die Nachricht, fak(n-1) zu errechnen und das Ergebnis an eben diesen customer zu senden, der dann seinerseits dafür sorgt, daß fak(n-1) mit n multipliziert wird und an das ursprüngliche Ziel stdout geschickt wird. Zur Berechnung von fak(n-1) kann es natürlich wieder notwendig sein, einen weiteren customer zu erzeugen und fak((n-1)-1) zu errechnen usw.
In ist der Ablauf der Berechnung von 3! noch einmal als event diagram verdeutlicht. Man sieht, wie drei Customer-Objekte erzeugt werden, die für die Multiplikationen sorgen und ihre Ergebnisse jeweils an den customer senden, der einen Schritt vorher erzeugt wurde. Der zuerst erzeugte customer kommuniziert schließlich mit dem ursprünglichen Ziel stdout.
Die Rekursivität wird an den drei Nachrichten deutlich, die der Fakultäts-Aktor sich selbst schickt. Diese werden im Bild durch die drei Pfeile dargestellt, die von der Fakultäts-Lebenslinie zu ihr zurückweisen.
Abbildung : rekursive Fakultätsberechnung
Andererseits bieten die zur Verfügung stehenden primitiven Operationen nur eine Art Maschinensprache, auf der programmiersprachliche Abstraktionen aufbauen müssen (z.B. zum automatischen Erzeugen von customers), will man größere Systeme modellieren.
Das ActorSpace-Modell basiert auf dem Actor-Modell und erweitert dies um einige neue Operationen. Hierzu gehören eine Möglichkeit, Aktoren in logischen Gruppen zusammenzufassen, ganze Gruppen von Objekten als Ziel einer Nachricht zu verwenden und allgemein eine abstraktere Art der Adressierung als die direkte Angabe einer Mailadresse.
Um dies zu realisieren werden einige neue Konzepte eingeführt: attributes, die Eigenschaften festlegen, die zur Auswahl eines Objekt benutzt werden können (über pattern-matching), actorspaces, die als Behälter für Aktoren dienen und schließlich capabilities, Zugriffsschlüssel, die es nur ihren Besitzern erlauben, bestimmte Aktionen auszuführen.
Wie schon kurz angesprochen, sind die Kommunikationsmöglichkeiten im ActorSpace-Modell vielfältiger als im Actor-Modell. Und zwar gibt es nun zwei verschiedene Kommunikations-Primitiven: zum einen das schon bekannte send to, das dazu dient, eine Nachricht an genau ein Objekt zu verschicken; zum anderen broadcast, mit dessen Hilfe es möglich ist, eine identische Nachricht an eine ganze Gruppe von Aktoren zu senden.
In direktem Zusammenhang mit der neuen Kommunikations-Primitive stehen zwei unterschiedliche Varianten, ein Ziel zu adressieren. Bei der herkömmlichen Adressierung über die eindeutige Mailadresse eines Objektes verhalten sich send to und broadcast identisch: Beide verschicken die angegebene Nachricht einfach an den spezifizierten Aktor. Die alternative Adressierung geschieht unter Angabe eines actorspace und eines Musters von Attributen. Es werden alle im angegebenen actorspace enthaltenen Aktoren mit dem Suchmuster verglichen (pattern-matching). Trifft das Muster zu, so wird das Objekt zu einem potentiellen Empfänger der Nachricht. Gibt es mehrere solcher Aktoren, so unterscheiden sich die beiden Kommunikationsoperationen: Wird send to verwendet, so wählt das System genau ein Objekt aus der Menge der potentiellen Adressaten aus und schickt diesem die Nachricht. Die genaue Vorgehensweise bei der Auswahl bleibt dem System überlassen und kann im allgemeinen nicht vorhergesagt werden (eine Implementierung könnte hier Effizienzaspekte berücksichtigen). Bei broadcast hingegen werden alle zutreffenden Objekte zu Empfängern derselben Nachricht.
Tritt der Fall ein, daß kein Objekt im gewünschten actorspace das Suchmuster erfüllt, so gibt es mehrere Möglichkeiten, darauf zu reagieren:
Die Nachricht wird einfach nicht verschickt, da kein passender Adressat vorhanden ist.
Die Nachricht wird gespeichert und an das erste Objekt gesendet, daß später im actorspace sichtbar wird und zu dem Muster paßt. D.h. die Nachricht wird an ein oder kein Ziel verschickt.
Bei broadcast kann die Nachricht auch persistent sein, d.h. sie wird an alle Aktoren geschickt, die zukünftig im angegebenen actorspace die Kriterien erfüllen. Allerdings erhält jedes Objekt die Nachricht höchstens einmal.
Bei einer Implementierung des Modells steht es einem frei, sich für eine der drei Varianten zu entscheiden. Bei der in [3] beschriebenen Implementierung wurde die zweite Variante gewählt.
Wie schon im Actor-Modell garantiert das Mail-System eine Auslieferung der Nachricht. Die Reihenfolge hingegen, in der die Nachrichten empfangen werden, kann nicht vorhergesagt werden. So kann es z.B. vorkommen, daß broadcast-Nachrichten bei zwei Aktoren in unterschiedlicher Reihenfolge in deren Mailqueues stehen. Wenn solche Nachrichten einer bestimmten Ordnung folgen sollen, so kann dies unter Benutzung spezieller Aktoren geschehen, die die Nachrichten mittels eines eigens dafür definierten Protokolls an die Ziele schicken und dabei den Erhalt der Reihenfolge garantieren. Im allgemeinen ist es aber effizienter, auf solch ein Verfahren zu verzichten, da es zusätzlichen Overhead bedeutet und die mögliche Parallelität einschränken kann.
Das ActorSpace-Modell führt zusätzlich zu den bekannten actor primitives einige neue Operationen ein, um actorspaces und Schlüssel zu erzeugen bzw. zu verwalten. Auf die beiden Operationen send_to und broadcast wurde weiter oben schon ausführlich eingegangen. Aus diesem Grund werden sie hier nicht noch einmal aufgeführt.
Mit dieser Operation werden neue, eindeutige Schlüssel erzeugt, eben sogenannte capabilities. Diese Schlüssel sind unteilbare Werte, die nicht manipuliert und nur durch Aufruf dieser Operation erzeugt werden können. Sie können aber in Nachrichten enthalten sein und somit auch anderen Aktoren als dem Aufrufer von make_capability bekannt gemacht werden.
Capabilities werden benötigt, um nur einigen ausgewählten Objekten bestimmte Operationen zu wie z.B. make_visible zu erlauben (s.u.). Dabei kann derselbe Schlüssel durchaus für mehrere verschiedene Aktoren benutzt werden.
New_actor ersetzt create aus dem Actor-Modell. Es dient ebenfalls zum Erzeugen neuer Aktoren und benötigt als Parameter die Angabe einer behaviour description. Neu ist die optionale Angabe einer capability, über die dann das Objekt vom Besitzer des Schlüssels in einem actorspace sichtbar oder unsichtbar gemacht werden kann. Wird kein solcher Schlüssel angegeben, so kann nur das Objekt selbst diese Operationen durchführen.
Zu beachten ist, daß der neu erzeugte Aktor noch in keinen actorspace enthalten ist und somit nicht über pattern matching adressiert werden kann. Um dies zu ermöglichen, muß das Objekt später explizit mit make_visible in einen actorspace aufgenommen werden.
New_actor liefert dem Aufrufer die eindeutige Mailadresse des neuen Objekts zurück, die in weiteren Operationen verwendet werden kann.
New_space dient zum Erzeugen eines actorspace. Der einzige Parameter ist eine capability. Actorspaces können neben Aktoren auch weitere actorspaces enthalten, so daß sich damit unter anderem hierarchische Strukturen aufbauen lassen. Der Besitz des zu einem actorspace gehörenden Schlüssels erlaubt es dem Besitzer, diesen in anderen actorspaces sichtbar oder unsichtbar zu machen oder ihn wieder zu entfernen.
Da ein actorspace ein passives Gebilde ist, kann dieser solche Aktionen nicht selbständig durchführen. Statt dessen muß dies von einem Aktor übernommen werden. Die Angabe der capability ist deshalb nicht optional.
Mit Hilfe dieser zentralen Operation wird ein Aktor in einem actorspace sichtbar gemacht, so daß er über die weiter oben angesprochenen Adressierungsmöglichkeiten (pattern matching) als Empfänger ausgewählt werden kann. Es müssen vier Parameter angegeben werden: der Aktor, der actorspace in den er aufgenommen werden soll, die Attribute, unter denen er in diesem actorspace sichtbar sein soll und die zum Aktor gehörende capability. Die Übergabe des Schlüssels stellt sicher, daß diese Operation nur vom Objekt selbst (dann entfällt die Angabe des Schlüssels) und vom Besitzer der Schlüssels aufrufbar ist.
Über den genauen Aufbau der Attribute sowie der Suchmuster wird im Modell nichts ausgesagt. Beispielsweise wäre eine Repräsentierung in der Form von Zeichenketten denkbar, wobei die Suchmuster dann aus bekannten regular expressions aufgebaut sein könnten.
Die inverse Operation make_invisible entfernt entfernt die Sichtbarkeit eines Aktors wieder unter Angabe seiner Mailadresse, des actorspace und der capability des Aktors. Außerdem können beide Operationen ebenso auf actorspaces angewandt werden.
Die Möglichkeit, Aktoren über Suchmuster in actorspaces anzusprechen, hat Auswirkungen auf die Lebenszeit der Objekte. Solche Aktoren, die in einem actorspace sichtbar sind, bleiben potentielle Empfänger, auch wenn ihre Mailadresse keinem anderen Objekt mehr bekannt ist (unter der Voraussetzung, daß der zugehörige actorspace einem anderen Objekt direkt oder indirekt, also durch Sichtbarkeit in einem anderen bekannten actorspace, bekannt ist). Daher können solche Objekte erst gelöscht werden, wenn sie mittels make_invisible unsichtbar gemacht wurden.
Das Objekt (oder die Objekte), die eine einem actorspace zugehörige capability besitzen, haben zusätzlich die Möglichkeit, den actorspace mit delete_space zu löschen. Die garbage collection hat dann die Möglichkeit, nun unsichtbar gewordene Objekte aus dem System zu entfernen. Das Löschen von actor_spaces kann aber ebenfalls analog zu Aktoren bei Nichtansprechbarkeit automatisch geschehen.
Zur Verdeutlichung betrachten wir eine sehr einfache Version einer verteilten Druckerverwaltung. In einem Netzwerk gebe es eine große Anzahl von Druckern, die sich in der verwendeten Beschreibungssprache unterscheiden (Postscript, ASCII, ...). Jedem Drucker ist ein Spool-Aktor zugeordnet, der die Druckjobs zwischenspeichert und zum Drucker schickt.
Eine Anwendung, die einen Job ausdrucken möchte, schickt diesen nicht direkt an den Spool-Aktor, sondern an einen darauf aufsetzenden PrintManager. Dieser ist ein Aktor, der dafür sorgt, daß der Spool-Aktor, den er verwaltet, nicht überlastet wird.
Die PrintManager werden dabei in einer einfachen Hierarchie von Actorspaces organisiert. Die Wurzel bildet der ActorSpace PrintManager, der Postscript, ASCII, etc. Als Unterräume enthält. In diesen Unterräumen schließlich sind die PrintManager-Aktoren selbst sichtbar.
Um nun einen Druckauftrag zu starten, wird einfach eine Nachricht an alle Objekte im gewünschten ActorSpace geschickt, also in etwa send-to '/PrintManager/Postscript/*' (job)3. Vom Mailsystem wird dann ein passender Manager ausgewählt, der die Nachricht erhält.
Der PrintManager selbst (s. ) ist sehr einfach gehalten. Er wurde mit zwei acquaintances erzeugt: dem zu verwaltenden Spooler und der Anzahl der anstehenden Druckjobs (bei Erzeugung Null). Bei Erhalt einer new_job-Nachricht überprüft er, ob weniger als 10 Druckjobs anstehen. Ist dies der Fall, so wird der Job an PrintSpool weitergeleitet und mittels become die Anzahl der Aufträge erhöht. Wenn Überlastung droht ([[congruent]]10 Aufträge), so wird die Nachricht einfach an irgendeinen anderen Manager derselben Gruppe geschickt. Außerdem entfernt sich der Aktor selbst aus seinem4 ActorSpace, um zu vermeiden, daß weitere Aufträge überhaupt erst zu ihm gelangen.
Wurde ein Auftrag erfolgreich ausgeführt, so sendet PrintSpool eine job_done-Nachricht an den Manager. Dieser dekrementiert dann einfach die Anzahl der noch ausstehenden Druckjobs und macht sich ab einer gewissen Schwelle sichtbar, um wieder Aufträge erhalten zu können.
behaviour Printmanager(PrintSpool, numJobs) { method new_job(printjob) // NEUER AUFTRAG { if (numJobs<10) // NOCH NICHT UEBERLASTET { send-to PrintSpool new_job(printjob) become Printmanager(PrintSpool, numJobs+1) } else // UEBERLASTUNG DROHT -> SCHICKE JOB WEITER { send-to "./*" new_job(printjob) make_invisible self "." } } method job_done // EIN AUFTRAG ERFOLGREICH BEENDET { become PrintManager(PrintSpool, numJobs-1) if (numJobs=5) make_visible self "." } }
Das ActorSpace-Modell bietet Erweiterungen, die wichtig sind, um größere Systeme von Aktoren zu organisieren. Durch die Einführung der actorspaces ist es möglich, Aktoren logisch zu gruppieren und mittels einer abstrakteren Adressierung (pattern matching) anzusprechen. Dies ermöglicht es beispielsweise neu in das System gekommenen Aktoren (Clients), die Dienste anderer Aktoren (Server) in Anspruch zu nehmen, deren genaue Adresse sie nicht zu kennen brauchen. Damit dies funktioniert, müssen natürlich bestimmte Konventionen über die Struktur der actorspaces und der Attribute getroffen werden.
Weiterhin können mehrere gleichwertige Server auf verschiedenen Orten in einem Netzwerk vorhanden sein. Will nun ein Client einen Dienst eines solchen Servers nutzen, so kann das System automatisch den Server auswählen, der am wenigsten belastet oder dem Auftraggeber am nächsten ist. Die Verwendung von broadcast kann dann dazu genutzt werden, um die Robustheit zu erhöhen, indem allen Server-Aktoren die gleiche Nachricht geschickt wird und deren Ergebnisse dann auf Abweichungen untersucht werden können.
Das Actor-Modell stellt eine äußerst einfache und leistungsfähige Grundlage dar, auf der parallele Systeme aufgebaut werden können. Die Erweiterungen des ActorSpace-Modells helfen bei der Erstellung größerer verteilter Systeme, indem weniger stark gekoppelte Beziehungen zwischen den Objekten ermöglicht werden.
Diese Leistungsfähigkeit bekommt man aber nicht umsonst: Anders als andere Konzepte, die beispielsweise bekannte sequentielle Programmiersprachen nur um neue Möglichkeiten erweitern, erzwingt das ActorSpace-Modell ein neues Programmier-Paradigma, das ein gewisses Umdenken erfordert. Hier kann man natürlich argumentieren, daß die herkömmlichen Programmiermodelle für parallele Abläufe zumindest zum Teil einfach ungeeignet sind, und deshalb ein neues Modell erforderlich ist.
Die Abläufe in diesem Modell bestehen zum Großteil aus dem Versand von Nachrichten. Dadurch hat die Implementierung des Mail-Systems sehr große Auswirkungen auf die Performance einer Anwendung. Ob eine reine Software-Lösung auf bestehenden Hardware-Architekturen eine ausreichende Effizienz bereitstellen kann, oder ob neue, die grundlegenden actor primitves unterstützende Prozessoren sinnvoll sind, müssen Implementierungen des Modells zeigen.
Gul Agha. Actors: A Model of Concurrent Computation in Distributed Systems. The MIT Press, Cambridge, Massachusetts, 1986.
Gul Agha. Concurrent Object-Oriented Programming. Communications of the ACM, 33(9):125-141, 1990.
Christian J. Callsen, Gul Agha. Open Heterogenous Computing in ActorSpace. Journal of Parallel and Distributed Computing, 21:289-300, 1994.
C.E. Hewitt. Viewing control structures as patterns of passing messages. Journal of Artificial Intelligence, 8-3:323-364, June 1977.