receive oder react
... [ Seminar Programmiersprachen und -Systeme ] ... [ Inhaltsverzeichnis ] ... [ Beispiel: parallele Primzahlberechnung ] ...
Übersicht: receive oder react
Probleme von receive
In Scala werden Actors durch Threads verwaltet. Die Anzahl der Threads ist idealerweise gleich der Anzahl der Prozessorkerne, die dem System zur Verfügung stehen. Durch das Aufrufen von Funktionen wie receive oder wait, wird ein wartender Actor im System als blockierter Thread dargestellt. Der blockierte Thread kann keine anderen Arbeiten mehr verrichten, bis den wartenden Actor wieder eine Nachricht erreicht.
Durch die Nutzung vieler Actors, die mittels receive ihre Nachrichten verarbeiten, werden also sehr viele Threads benötigt. In Java sind Threads aber leider nicht leichtgewichtig und daher kann die JVM nur einige tausend Threads verwalten. Im Vergleich dazu kann die JVM aber Millionen von Objekten verwalten.
react als Alternative
Also bietet Scala eine weitere Möglichkeit Nachrichten zu verarbeiten. Dies geschieht mittels der react Methode. Diese arbeitet event basiert. Es wird kein Thread blockiert und dadurch kann der Thread im Hintergrund Arbeiten verrichten, die von Actors initiiert werden.
Der wesentliche Unterschied zwischen den beiden Methoden ist, dass react niemals returned. Dadurch muss man sich nach der Verarbeitung einer Nachricht manuell darum kümmern, wie der Rest der Verarbeitung vonstatten gehen soll.
Besonderheiten von react
Es bieten sich zwei verschiedene Möglichkeiten react zu nutzen. Die aus den bisherigen Beispielen bekannte Methode receive innerhalb einer while(true) Schleife zu nutzen, lässt sich problemlos mit react umsetzen. Dazu gibt es die loop Methode, die ohne Abbruchbedingung arbeitet. Innerhalb dieser darf react genutzt werden, was in while Schleifen niemals erlaubt ist.
Beispiel für Nutzung einer react Methode statt receive mit while(true):
...
while(true){
receive{...}
}
Hieraus wird einfach folgendes:
...
loop{
react{...}
}
Wenn nun aber keine Endlosschleife genutzt werden soll, muss die weitere Verarbeitung explizit geplant werden. Hierzu bietet sich die act Methode an, die nach der Verarbeitung einer Nachricht wieder aufgerufen wird, falls eine weitere Verarbeitung ansteht.
Hierzu ein Beispiel zur Nutzung von act als top-level Methode für die weitere Verarbeitung:
object reactAct extends Actor {
def act() {
react {
case (name : String, alter : Int) =>
println(name + " ist " + alter + " Jahre alt")
act()
case "ende" =>
println("wird beendet")
exit()
case msg =>
println("ich weiss nicht was ich machen soll")
act()
}
}
}
//Input:
reactAct.start()
reactAct ! ("Meier", 42)
reactAct ! "egal"
reactAct ! "ende"
Meier ist 42 Jahre alt
ich weiss nicht was ich machen soll
wird beendet
Das Beispielprogramm
Die react Methode zu nutzen, ist zwar etwas komplizierter, kann aber durch die Konservierung von Threads zu einem deutlich effizienteren Programm führen.
Verschachteltes react
Eine weitere Option, die von react geboten wird, ist das verschachtelte Verarbeiten von Nachrichten. Dazu wird innerhalb eines case Blockes erneut ein react definiert. Dadurch können Nachrichten sequenziell verarbeitet werden.
Hierzu ein einfaches Beispiel:
class NestedMsgAct extends Actor {
def act(){
loop {
react {
case simpleMsg : SimpleMsg =>
println("Einfache Nachricht erhalten")
case firstMsg : FirstMsg =>
println("Ersten Teil der Nachricht erhalten")
react {
case secondMsg : SecondMsg =>
println("Geschachtelte Nachrichten erhalten: " + firstMsg.msg + " ; " + secondMsg.msg)
exit()
}
}
}
}
}
//Input:
val nestedMsgAct = new NestedMsgAct()
nestedMsgAct.start()
nestedMsgAct ! SimpleMsg()
nestedMsgAct ! FirstMsg("Ich bin die erste Nachricht")
nestedMsgAct ! SecondMsg("Ich bin die zweite Nachricht")
Einfache Nachricht erhalten
Ersten Teil der Nachricht erhalten
Geschachtelte Nachrichten erhalten: Ich bin die erste Nachricht ; Ich bin die zweite Nachricht
Das Beispielprogramm
Was macht react?
Der grundlegende Unterschied zwischen react und receive ist, daß receive thread-basiert arbeitet, während react event-basiert arbeitet.
Dazu wird dem Actor ein Feld continuation hinzugefügt, welches folgenden Typen hat: PartialFunction[Any, unit]. Diese partielle Funktion enthält die Weiterverarbeitung des Actors sobald er keine weitere Nachricht zur Verarbeitung in seiner Mailbox hat. Da die Funktion den kompletten Ausführungsstatus des Actors enthält, kann die Weiterverarbeitung auch zu einem späteren Zeitpunkt veranlasst werden. Z.B. wenn eine Nachricht an den Actor gesendet wird, die ihm bekannt ist.
Zusätzlich gibt es noch die Variable suspended, die darüber Auskunft geben soll ob ein Actor gerade wartet oder nicht. Falls der Actor also "suspended" ist, dann muss continuation eine Weiterverarbeitung enthalten. Wenn also eine Nachricht bekannt ist, wird continuation auf diese Nachricht angewandt.
Hierzu ein Beispiel wie die send Methode implementiert ist:
def !(msg: Any): unit =
if (suspended && continuation.isDefinedAt(msg))
try { continuation(msg) }
catch { case SuspendActorException => }
else mailbox += msg
Quelle: Actors that Unify Threads and Events von Philip Haller und Martin Odersky (Seite 12)
Die SuspendActorException muss abgefangen werden, da ein verschachteltes react in continuation möglich wäre, welches den Actor wieder warten lassen würde.
Wenn react benutzt wird, basiert die Kommunikation zwischen Actors also nicht mehr auf Threads, sondern auf events bzw. Exceptions.
... [ Seminar Programmiersprachen und -Systeme ] ... [ Inhaltsverzeichnis ] ... [ Beispiel: parallele Primzahlberechnung ] ...