Warnung

Dieser Seitenbereich bezieht sich auf die ursprügliche Scala Aktor Implementierung. Die ursprüngliche Scala Aktor-Implementierung gilt seit der Version 2.10.0 von Scala als deprecated. Der Compiler gibt mit der Version 2.10.0 noch keine entsprechende Warnung aus, was aber wahrscheinlich mit einer der nächsten Versionen erfolgt. In einem zukünftigen Major-Release ist damit zu rechnen, dass die ursprünglichen Scala-Aktoren aus der Scala-Distribution entfernt werden.

Die neue Standard Aktor Implementierung der Scala-Distribution ist das Akka-Framework, welches auch Bestandteil des Typesafe-Stacks ist.

Dieses Tutorial ist nur für den Interessant, der sich mit "Alt - Anwendungen" beschäftigen muss oder eine Scala Version vor 2.1.0 verwenden muss. Jeder der eine neue Anwendung entwickelt und Scala in der Version 2.1.0 oder höher verwenden kann, sei angeraten, direkt Akka als Framework für Nebenläufigkeit zu verwenden.

Einstieg

Die nebenläufige Programmierung wird in Scala durch ein Aktor-Modell unterstützt. Das Modell ist dabei nicht Kernbestandteil der Sprache, sondern ist in der Scala Aktor Bibliothek realisiert.

Einen eigenen Aktor definiert man, indem die entsprechende Klasse das Trait scala.actors.Actor und dessen Methode act() implementiert. Das folgende Beispiel MySimpleActor zeigt beispielhaft die Definition eines einfachen Aktors.

import scala.actors._

class MySimpleActor extends Actor{

  def act(){
    for (i <- 0 to 10) println("Acting value: "+i)
  }
}
itmapa.de - X2H V 0.5

Gestartet wird ein Aktor durch Aufruf der Methode start() des Traits scala.actors.Actor.

scala> val myActor = new MySimpleActor()
myActor: MySimpleActor = MySimpleActor@11b99c4

acala> myActor.start
Acting value: 0
Acting value: 1
Acting value: 2
Acting value: 3
Acting value: 4
Acting value: 5
Acting value: 6
Acting value: 7
Acting value: 8
Acting value: 9
Acting value: 10
res5: scala.actors.Actor = MySimleActor@11b99c4

scala>
            

Ruft man die Methode act() des Aktors direkt auf wird die Methode wie jede andere Methode ausgeführt. Die Methode läft dann allerdings nicht nebenläufig zum umgebenden Kontext ab.

TwoActors

Das vorherige Beispiel hatte nur einen Aktor. Die nebenläufige Arbeitsweise war im Beispiel nicht zu erkennen. Um die nebenläufige Arbeitsweise besser zu sehen, folgt nun ein Beispiel, in dem zwei Aktoren verwendet werden.

import scala.actors.Actor

object TwoActors {
    def main(args: Array[String]) {
    val myActor1 = new MyActor1
    val myActor2 = new MyActor2

    myActor1.start()
    myActor2.start()
  }
}

class MyActor1 extends Actor{
  def act(){
    for (i <- 0 to 10) println("MyActor1: "+i)
  }
}

class MyActor2 extends Actor{
  def act(){
    for (i <- 0 to 10) println("MyActor2: "+i)
  }
}
itmapa.de - X2H V 0.5

Eine mögliche Ausgabe des Programmes kann wie folgt aussehen:

MyActor1: 0
MyActor2: 0
MyActor2: 1
MyActor2: 2
MyActor2: 3
MyActor2: 4
MyActor2: 5
MyActor2: 6
MyActor1: 1
MyActor1: 2
MyActor1: 3
MyActor2: 7
MyActor2: 8
MyActor2: 9
MyActor1: 4
MyActor1: 5
MyActor2: 10
MyActor1: 6
MyActor1: 7
MyActor1: 8
MyActor1: 9
MyActor1: 10
            

Aktoren und Nachrichtenaustausch

Ein grundlegender Ansatz in der nebenläufigen Programmierung mit Aktoren ist es, das die Kommunikation der beteiligten Aktoren nicht mithilfe gemeinsam genutzter Variablen erfolgt. Die Kommunikation erfolgt beim Aktor Ansatz mit Hilfe von Nachrichten, die dem jeweiligen Actor über die Methode ! übermittelt werden.

Um diese Art der Kommunikation zu ermöglichen, besitzt jeder Aktor eine Nachrichtenbox, in der die eingehenden Nachrichten gesammelt werden. Die Arbeit des Aktors wird durch den Eingang einer Nachricht nicht unterbrochen, sondern er kann seine Nachrichten zu einem geeigneten Zeitpunkt abrufen und darauf reagieren. Auch ist es möglich, dass ein Aktor gar nicht auf eingehende Nachrichten reagiert. Das Objekt, welches eine Nachricht an einen Aktor sendet, arbeitet direkt nach dem Versand weiter, ohne (direkt) auf eine Antwort des Aktors zu warten.

Ein Aktor kann eine Nachricht aus seiner Nachrichtenbox mithilfe eines receive Blockes bearbeiten. Befindet sich keine Nachricht in der Nachrichtenbox, wartet der Aktor, bis er eine Nachricht empfängt. Die eingehenden Nachrichten können durch ein Pattern Matching geeignet verarbeitet werden.

Das nachfolgende Beispiel MySimpleMessageActor zeigt einen Aktor, welcher in einer Endlosschleife eingehende Nachrichten verarbeitet.

import scala.actors.Actor

class MySimpleMessageActor extends Actor{
  def act(){
    loop{
      receive {
        case i : Int => println("got an Int message: "+i)
        case d : Double => println("got a Double message: "+d)
        case s : String => println("got a String message: "+s)
        case _ => println("unknown message type")
      }
    }
  }
}
itmapa.de - X2H V 0.9

Im Beispiel MySimpleMessageActor wird entsprechend des Typs (Objekt - Typ) der Nachricht eine Ausgabe auf der Systemausgabe gemacht. Unterschieden werden hier Nachrichten vom Typ

  • Int
  • Double
  • String
  • keine der oben genannten

Die Anweisung loop ist eine Kurzschreibweise für while(true)

Das nachfolgende Beispielprogramm erzeugt einen Actor vom Typ MySimpleMessageActor, startet ihn und sendet vier unterschiedliche Nachrichten an den Actor.

object TestMySimpleMessageActor {
  def main(args: Array[String]) {
    val mySimpleMessageActor = new MySimpleMessageActor()
    mySimpleMessageActor.start()
    
    mySimpleMessageActor ! 12.34
    mySimpleMessageActor ! 3    
    mySimpleMessageActor ! "a message"
    mySimpleMessageActor ! 123f
  }
}
itmapa.de - X2H V 0.8

Als Ergebnis des Programmablaufs erfolgt folgende Ausgabe auf der Systemausgabe:

got a Double message: 12.34
got an Int message: 3
got a String message: a message
unknown message type
          

Nicht blockierte Aktoren

Bei der Verwendung der Methode receive ist der Aktor solange blockiert, bis eine Nachricht eintrifft. Gibt es keinen weiteren Aktor / Thread, der diesem Aktor eine Nachricht sendet, verschwendet dieser Aktor unnötig Ressourcen. Abhilfe schafft hier die Methode receiveWithin(msec: Long) von Actor. Trifft innerhalb von msec Millisekunden keine Nachricht ein, wird ein scala.actors.TIMEOUT als Nachricht übergeben, worauf der Aktor entsprechend reagieren kann.

Das nachfolgende Beispiel ist eine Erweiterung von MySimpleMessageActor und TestMySimpleMessageActor, welches die Verwendung von receiveWithin zeigen soll.

import scala.actors.Actor
import scala.actors.TIMEOUT

class MySimpleMessageActor2 extends Actor{
  def act(){
    loop{
      receiveWithin(1000) {
        case i : Int => println("got an Int message: "+i)
        case d : Double => println("got a Double message: "+d)
        case s : String => println("got a String message: "+s)
        case TIMEOUT => println("Timeout")
        case _ => println("unknown message type")
      }
    }
  }
}

object TestMySimpleMessageActor2 {
  def main(args: Array[String]) {
    val mySimpleMessageActor2 = new MySimpleMessageActor2()
    mySimpleMessageActor2.start()
    
    mySimpleMessageActor2 ! 12.34
    mySimpleMessageActor2 ! 3    
    mySimpleMessageActor2 ! "a message"
    mySimpleMessageActor2 ! 123f
    
    while (true) {
      mySimpleMessageActor2 ! "Message in a loop"
      Thread.sleep(2000)
    }
  }
}
itmapa.de - X2H V 0.9

Eine mögliche Ausgabe des Programms ist:

got a Double message: 12.34
got an Int message: 3
got a String message: a message
unknown message type
got a String message: Message in a loop
Timeout
Timeout
Timeout
got a String message: Message in a loop
Timeout
Timeout
Timeout
got a String message: Message in a loop
Timeout
Timeout
...