Helsingin yliopisto / tietojenkäsittelytieteen laitos / Ohjelmointitekniikka (Scala) / © Arto Wikla 2010

30 Aktoreista ja rinnakkaisuudesta lyhyesti

Muutettu viimeksi 26.4.2010 / Sivu luotu 23.4.2010 / [oppikirjan esimerkit] / [Scala]

Sivun sisältöä:

Tässä luvussa nähdään vain Scalan aktoreiden perusidea. Rinnakkaisuuden syvällisempi käsittely säästetään muille kursseille.

Perusidea

Scalassa on toki käytettävissä Javan koko rinnakkaisohjelmointikalusto, mutta sen lisäksi kielen kirjastossa on välineet ns. aktorimallin käyttämiseen rinnakkaisuuden toteuttamisessa. Kirjasto osaltaan myös havainnollistaa Scalan voimaa omien kontrollirakenteiden toteuttamisessa.

Yksi rinnakkaisohjelmoinnin monista ongelmista on jaetun muistin suojaus. Aktorimallissa jaettu muisti ei tuota ongelmia, koska aktorit eivät jaa muistia! Aktorit sen sijaan lähettelevät toisilleen viestejä ja vastaanottavat muilta aktoreilta tulleita viestejä. Viestien vastaanottoon aktorilla on postilaatikko.

Aktoreita voi luoda scala.actors.Actor-luokan aliluokkina (tai tässä "aliolioina") ohjelmoimalla act()-metodin:

  import scala.actors._

  object SillyActor extends Actor {
    def act() {
      for (i <- 1 to 5) {
        println("I'm acting!")
        Thread.sleep(700)
      }
    }
  }
  object SeriousActor extends Actor {
    def act() { 
      for (i <- 1 to 5) {
        println("To be or not to be.")
        Thread.sleep(1000)
      }
    }
  }
Aktori käynnistetään start-metodilla:
  object Silly extends Application {
    SillyActor.start
    SeriousActor.start
  }
// Tulostaa hidastellen:
// I'm acting!
// To be or not to be.
// I'm acting!
// To be or not to be.
// I'm acting!
// To be or not to be.
// I'm acting!
// I'm acting!
// To be or not to be.
// To be or not to be.

Aktorin voi valmistaa myös metodilla scala.actors.Actor.actor-metodilla:
  import scala.actors.Actor._

  object Hamlet extends Application {
    val hamlet = actor {
      for (i <- 1 to 5) {
        println("That is the question.")
        Thread.sleep(1000)
      }
    }   
  }
Näin luotu aktori käynnistyy saman tien ilman mitään start-pyyntöä.

Viestit

Viestejä lähtetään syntaksilla
   aktori ! viesti
Kun aktori lähettää viestin, se ei pysähdy odottamaan mitään. Kun aktori saa viestin, sen toiminta ei keskeydy, mutta halutessaan aktori voi mennä lukemaan postilaatikkoaan lauseella receive:
  receive {
    case jotakin: Tyyppi => toimintaa
    case jotakinmuuta: ToinenTyyppi => muuta toimintaa
    ...
    case eityyppiä => toimintaa muiden kuin nimettyjen tyyppien tapauksessa
  }
Jos postilaatikosta ei löydy mitään vastaanotettavan tyyppistä viestiä, aktori odottaa, kunnes sellainen tulee. Jos viimeisenä on tyypitön case-vaihtoehto, minkä tahansa tyyppinen viesti vastaanotetaan.

Esimerkki.

import scala.actors.Actor._

object Kaiku extends Application {

  val kaiuttaja = actor {
    while (true) {
      receive {  // tälle mikä vain kelpaa
        case msg =>
          println("vastaanotettu viesti: "+ msg)
      }
    }
  }
  val lukuTehdas = actor {
    for (i <- 1 to 10) {
      kaiuttaja ! i
      Thread.sleep(700)
    }
  }
  val merkkijonoTehdas = actor {
    for (i <- 1 to 10) {
      kaiuttaja ! "kissa numero " + i
      Thread.sleep(1000)
    }
  }
}
Esimerkkitulostus (huom: ohjelman saa päättymään ctrl-c:llä):
vastaanotettu viesti: kissa numero 1
vastaanotettu viesti: 1
vastaanotettu viesti: 2
vastaanotettu viesti: kissa numero 2
vastaanotettu viesti: 3
vastaanotettu viesti: kissa numero 3
vastaanotettu viesti: 4
vastaanotettu viesti: 5
vastaanotettu viesti: kissa numero 4
vastaanotettu viesti: 6
vastaanotettu viesti: kissa numero 5
vastaanotettu viesti: 7
vastaanotettu viesti: 8
vastaanotettu viesti: kissa numero 6
vastaanotettu viesti: 9
vastaanotettu viesti: kissa numero 7
vastaanotettu viesti: 10
vastaanotettu viesti: kissa numero 8
vastaanotettu viesti: kissa numero 9
vastaanotettu viesti: kissa numero 10

Esimerkki, jossa eri tyyppisiä viestejä käsitellään eri tavoin:

import scala.actors.Actor._

object EriKaiku extends Application {

  val kaiuttaja = actor {
    while (true) {
      receive {
        case msg: String =>
          println("vastaanotettu merkkijono:   "+ msg)
        case msg: Int =>
          println("vastaanotettu kokonaisluku: "+ msg)
        case msg =>
          println("vastaanotettu jotakin:      "+ msg)
      }
    }
  }
  val lukuTehdas = actor {
    for (i <- 1 to 4) {
      kaiuttaja ! i
      Thread.sleep(700)
    }
  }
  val merkkijonoTehdas = actor {
    for (i <- 1 to 4) {
      kaiuttaja ! "kissa numero " + i
      Thread.sleep(1000)
    }
  }
  val jokinTehdas = actor {
    for (i <- 1 to 3) {
      kaiuttaja ! List(3,1)
      Thread.sleep(500)
      kaiuttaja ! Tuple("hau", 42, 'miau)
    }
  }
}
Esimerkkitulostus (huom: ohjelman saa päättymään ctrl-c:llä):
vastaanotettu kokonaisluku: 1
vastaanotettu merkkijono:   kissa numero 1
vastaanotettu jotakin:      List(3, 1)
vastaanotettu jotakin:      (hau,42,'miau)
vastaanotettu jotakin:      List(3, 1)
vastaanotettu kokonaisluku: 2
vastaanotettu merkkijono:   kissa numero 2
vastaanotettu jotakin:      (hau,42,'miau)
vastaanotettu jotakin:      List(3, 1)
vastaanotettu kokonaisluku: 3
vastaanotettu jotakin:      (hau,42,'miau)
vastaanotettu merkkijono:   kissa numero 3
vastaanotettu kokonaisluku: 4
vastaanotettu merkkijono:   kissa numero 4

Tuotantoketju

Yksi paljon käytetty ohjelmiston arkkitehtuurin malli on tuottaja-kuluttajamalli. Aktoreilla sellaisen ohjelmointi on luontevaa:
import scala.actors.Actor._

object Tuotantoketju extends Application {

  val alkutuotanto = actor {
    while (true) {
      jalostus ! Math.random
      Thread.sleep(500)
    }
  }
  val jalostus = actor {
    while (true) {
      receive {
        case msg: Double => koristelu ! (msg * 1000 toInt)
      }
    }
  }
  val koristelu = actor {
    while (true) {
      receive {
        case msg => myynti ! "** " + msg + " **"
      }
    }
  }
  val myynti = actor {
    var lkm = 0
    while (true) {
      receive {
        case msg => ostaja ! msg
        lkm += 1
      }
      println("Myyty " + lkm + "kpl")
    }
  }
  val ostaja = actor {
    while (true) {
      receive {
        case msg => println("Ostin juuri uuden hienon " + msg)
      }
    }
  }
}
Esimerkkitulostusta (huom: ohjelman saa päättymään ctrl-c:llä):
Myyty 1kpl
Ostin juuri uuden hienon ** 858 **
Myyty 2kpl
Ostin juuri uuden hienon ** 206 **
Myyty 3kpl
Ostin juuri uuden hienon ** 524 **
Myyty 4kpl
Ostin juuri uuden hienon ** 899 **
Myyty 5kpl
Ostin juuri uuden hienon ** 948 **
Myyty 6kpl
Ostin juuri uuden hienon ** 895 **
Myyty 7kpl
Ostin juuri uuden hienon ** 53 **
...