22.2. Aktorzy — przykład 1

W języku Java programowanie współbieżne realizowane jest przy pomocy wątków. Z wątków można jawnie korzystać również w języku Scala, ale język ten, a raczej jego biblioteka standardowa, oferuje także inne podejście — w Scali można tworzyć programy korzystające z tak zwanych aktorów. Aktora można sobie wyobrazić jako wątek ze skrzynką pocztową. Nie jest to ścisła definicja, ale na początek wystarczy. Do aktorów można wysyłać komunikaty. Implementując metodę receive aktora definiujemy jak ma on reagować na otrzymane komunikaty. Implementacja aktorów została zmieniona między wersjami 2.9 i 2.10 języka. Nowa wersja języka używa implementacji aktorów z biblioteki Akka i ta implementacja jest opisana w tym rozdziale. Należy jednak zauważyć, że biblioteka Akka zawiera bogatszą funkcjonalność w porównaniu do tego, co jest zawarte w standardowej dystrybucji języka Scala. Ten rozdział nie zastępuje dokładnego opisu biblioteki Akka.

Rozpoczniemy prezentowanie aktorów od przykładów implementujących program zawierający dwóch aktorów grających role klienta i serwera. Serwer generuje liczbę całkowitą z przedziału od 0 do 9, a zadaniem klienta jest odgadnięcie tej liczby. Klient podejmuje kolejne próby przesyłając serwerowi komunikaty będące liczbami całkowitymi. Serwer odpowiada klientowi przesyłając komunikaty będące symbolami. Jeśli przesłana serwerowi liczba jest równa liczbie wygenerowanej przez serwer, odpowiada on komunikatem 'correct i kończy działanie. W przypadku, gdy przesłana liczba jest zbyt mała, serwer odpowiada komunikatem 'tooSmall i kontynuuje pracę. Wreszcie w przypadku, gdy przesłana liczba jest zbyt duża, serwer odpowiada komunikatem 'tooBig i również kontynuuje pracę. Program jest zaimplementowany w pliku Guess1.scala. Poniższy listing pokazuje pierwszą część tego pliku.

Plik Guess1.scala:
import akka.actor._ 
object Guess1 extends App {
  val system = ActorSystem("Guess1") 
  val server = system.actorOf(Props[GuessServer1]) 
  val client = system.actorOf(Props(classOf[GuessClient1],server)) 
  client ! 'startGuessing 
}

W wierszu importujemy komponenty pakietu akka.actor, które są używane w dalszej części programu. W wierszu tworzymy tak zwany system aktorów, pod którego kontrolą będą działać instancje aktorów. W wierszach i uruchamiamy instancje obu aktorów działających w programie — serwera i klienta. Klient jest zaimplementowany w klasie GuessClient1, a serwer w klasie GuessServer1. Klasa klienta ma jeden parameter. Instancji aktorów nie tworzymy samodzielnie. Zajmuje się tym za nas system aktorów. Metodzie actorOf systemu aktorów przekazujemy instancję klasy Props, reprezentującą przepis na utworzenie aktora. Wartość server z wiersza zostanie przekazana jako argument konstruktorowi klasy GuessClient1. W wierszu , za pomocą metody ! przesyłamy klientowi pierwszą wiadomość.

Plik Guess1.scala:
class GuessClient1(server: ActorRef) extends Actor { 
  println("GuessClient1: starting")
  private[this] var n: Int = 5 
  def receive = { 
    case 'startGuessing => 
      send(n)
    case 'correct => 
      println(s"GuessClient1: solution found: $n")
      context.system.shutdown()
    case 'tooSmall => 
      println("GuessClient1: too small")
      n = n + 1
      send(n)
    case 'tooBig => 
      println("GuessClient1: too big")
      n = n - 1
      send(n)
  }
  def send(msg: Int) {
    println(s"GuessClient1: sending $msg")
    server ! msg 
  }
}

Klasa GuessClient1 rozszerza cechę Actor (wiersz ) i ma jeden parametr — server — będący adresem aktora grającego rolę serwera. Taki adres jest reprezentowany jako typ ActorRef. Klasa implementuje metodę receive (wiersz ), która definiuje sposób reakcji aktora na przychodzące do niego komunikaty. Metoda receive powinna zwrócić funkcję częściową implementującą obsługę komunikatów. Aktor klienta reaguje na cztery różne komunikaty. Pierwszym z nich jest wartość 'startGuessing (wiersz ). Ten komunikat jest wysyłany tylko raz, na początku działania programu (wiersz ) i rozpoczyna proces odgadywania liczby. W reakcji na niego klient wysyła do serwera komunikat będący liczbą całkowitą. Klient pamięta ostatnio próbowaną liczbę w zmiennej prywatnej n. Odgadywanie zaczyna się od wartości 5 (wiersz ). Instrukcja z wiersza powoduje przesłanie do serwera komunikatu zawierającego liczbę n. Metoda ! jest wywoływana na obiekcie, który jest adresem odbiorcy komunikatu. Parametrem tej metody jest przesyłany komunikat. W przykładzie jest to liczba typu Int. Metoda ! działa asynchronicznie — działanie klienta nie jest blokowane. Po wykonaniu instrukcji wysyłającej komunikat obsługa komunikatu 'startGuessing kończy się i aktor może obsłużyć następny komunikat ze swojej skrzynki pocztowej. Jeśli w skrzynce pocztowej aktora nie ma komunikatu do obsłużenia, działanie aktora jest wstrzymywane.

Serwer odpowiada na komunikat klienta przesyłając mu jeden z trzech komunikatów: 'correct, 'tooSmall lub 'tooBig. W przypadku odpowiedzi 'correct (wiersz ) klient wyświetla komunikat zawierający odgadniętą liczbę oraz wywołuje context.system.shutdown(), żeby zakończyć działanie całego systemu aktorów. Wartość context jest odwołaniem do kontekstu aktora i jest typu ActorContext. Metoda system kontekstu zwraca system aktorów, a metoda shutdown zatrzymuje go. W przypadku odpowiedzi 'tooSmall (wiersz ) lub 'tooBig (wiersz ) klient zmienia wartość zmiennej n o wartość 1 w górę lub w dół i przesyła do serwera kolejny komunikat z nową wartością.

Klasa GuessServer1 jest implementacją aktora grającego rolę serwera.

Plik Guess1.scala:
class GuessServer1 extends Actor { 
  val n = scala.util.Random.nextInt(10)
  println("GuessServer1: starting")
  def receive = { 
    case x:Int if x > n => 
      send(sender, 'tooBig)
    case x:Int if x < n => 
      send(sender, 'tooSmall)
    case x:Int => 
      send(sender, 'correct)
  }
  def send(sender: ActorRef, msg: Symbol) {
    println(s"GuessServer1: sending $msg")
    sender ! msg
  }
}

Ta klasa również rozszerza cechę Actor (wiersz ) oraz implementuje metodę receive (wiersz ). Konstruktor inicjalizuje wartość n, czyli liczbę którą należy odgadnąć. Funkcja częściowa zwracana przez metodę receive obsługuje komunikaty w postaci liczby całkowitej, ale dozory wydzielają z nich trzy przypadki. Jeśli otrzymana liczba jest równa n (wiersz ), serwer odsyła nadawcy komunikat 'correct. Jeśli otrzymana liczba jest większa (wiersz ) lub mniejsza (wiersz ) od n, serwer odsyła komunikat 'tooBig lub 'tooSmall. W każdym z tych przypadków wiadomość odsyłana jest do nadawcy otrzymanej wiadomości dzięki wykorzystaniu metody sender, zwracającej adres nadawcy.

Poniżej przedstawiony jest przykładowy rezultat wykonania programu.

$ scala Guess1
GuessClient1: starting
GuessServer1: starting
GuessClient1: sending 5
GuessServer1: sending 'tooBig
GuessClient1: too big
GuessClient1: sending 4
GuessServer1: sending 'tooBig
GuessClient1: too big
GuessClient1: sending 3
GuessServer1: sending 'tooBig
GuessClient1: too big
GuessClient1: sending 2
GuessServer1: sending 'correct
GuessClient1: solution found: 2

Język programowania Scala Wydanie 2. Copyright © Grzegorz Balcerek 2016

Licencja Creative Commons

Ten utwór jest dostępny na licencji Creative Commons Uznanie autorstwa-Na tych samych warunkach 4.0 Międzynarodowe.

Oracle and Java are registered trademarks of Oracle and/or its affiliates. Other names may be trademarks of their respective owners.