Scala - Actors and Concurrency

Sometimes it helps in designing a program to specify a program to specify that things happen independently,  in parallel, concurrently.  Scala augument Java's native support by adding "actors". Actors provide a concurrency model that is easier to work with and can, therefore help you avoid many of the difficulties of using Java's actors library, what we will translate the the single-thread circuit simulation code of Stateful object into actor based multi-threaded version. 

Trouble in paradise

Show you some of the troubles that we have in concurrently programming. 
reason :
Shared data and synchronized sections, and locking mechanism. 

problems:

  1. difficult to build robust system 
  2. testing is difficult 
  3. balance between possibility for race condition and deadlock
  4. concurrent collection is good, but it does not solve issues. 
Scala's solution is to provide a alternative, share-nothing, message passing model that programer find much easier to reason about. 


Actors and message passing

An actor is a thread-like entity that has a mailbox for receiving messages, to implement an actor, you subclasss scala.actors.Actor and implement the act method.  An example shows as follow.

import scala.actors._
object SillyActor extends Actor { 
  def act() { 
    for (i <- 1 to 5) {
      println("I am acting..")
      Thread.sleep(1000)
    }
  }
}

SillyActor.start()
as you can see, that the actor before does not use the mailbox. 

to use it, as you can see, SillyActor.start().. 

the result is as follow. 

I'm acting!
I'm acting!
I'm acting!
I'm acting!
and to start more than one actor together, as follow. 

import scala.actors._
object SeriousActor extends Actor { 
  def act() { 
    for (i <- 1 to 5) {
      println("To be or not to be.")
      Thread.sleep(1000)
    }
  }
}

SeriousActor.start()
when running it, now you can have 
To be or not to be .
I'm acting
To be or not to be. 
I'm acting 
You can create an actor using utilities method named actor in object scala.actors.Actor:
// utility method named actor in object scala.actors.Actor
import scala.actors.Actor._

val seriousActor2 = actor { 
  for (i <- 1 to 5)
    println("That is the question.")
    
  Thread.sleep(1000)
}
How to communicate without using shared memory and locks? Actrors communicate by sending each other messages.  You send a message by using ! method, like this:
// how do they work together. 
// Actors communicate by sending each other messages, you send a message by using the ! method, like this:

SillyActor ! "hi there"
since we can send message to actor, and how do we process the message sent to the mailbox, it has a receive message, receiving a partial functions. 
import scala.actors.Actor._
val echoActor = actor { 
  while (true) {
    receive { 
      case msg => 
        println("received message: " + msg)
    }
  }
}
Now to send message to it , it can respond. 
echoActor ! "Hi there" // received message : hi there
echoActor ! 15         // received messgae : 15
You may ask what may happen if the message that sent to an actor does not match the partial function, remember that you have isDefinedAt for partial function. the rule is this: when the mailbox contains no message for which isDefinedAt retrurns true, the actor on which receive was invoked will block until a matching message arrives. 
// as we know actor handler is a partial function, when the partial function has isDefinedAt method,when mailbox has message for which isDefinedAt return true, the actor execute, otherwise, the reactor block 
val intActor = actor {
  receive { 
    case x : Int => // I only want Ints 
      println("Got an Int: " + x)
  }
}
you pass the following, silently ignored.. 
intActor ! "hello" // has nothing
intActor ! math.Pi // has nothing
but if you have this: 
intActor ! 12      // an int printed out. 

Treating native threads as actor

Every native thread is also usable as an actor. However, you cannot use Thread.currentThread directly, because it does not have the necessary methods. 
You sould use Actor.self if you want to view the current thread as an actor. 

import scala.actors.Actor._

// Every native thread is also usable as an actor. However, you cannot use Thread.currentThread directly, because it does not have the necessary methods. 
// you sould use Actor.self if you want to view the current thread as an actor. 
self ! "hello"
this facility is especially useful useful for debugging actors from the interactive shell. 
import scala.actors.Actor._

// Every native thread is also usable as an actor. However, you cannot use Thread.currentThread directly, because it does not have the necessary methods. 
// you sould use Actor.self if you want to view the current thread as an actor. 
self ! "hello"

self.receive  { case x => x }
you can use technique, it is better to use a variant of receive  called receiveWithin, you can then specify a timeout in milliseconds. 
// a variant will block until timeout or a value is received.
self.receiveWithin(1000) { case x => x } // wait a sec!

Better performance through thread reuse

Thread is not cheap, as you can create millions of objects in java virtual machine, but only thousands of threads. Worse, switching threads often takes hundreds if not thousands of processor cycles.

To help you conserve threads, Scala provides an alternative to the usual receive method called react. unlike receive, react does not return after it find and process a message. it result type is Nothing (not Unit, though I am not sure what is different between result type of Unit or Nothong, maybe they are the same). 

The key here is that because react does not return, the implementation does not need to preserve the call stack of current thread. instead the library can reuse the current thread for the next actor that wakes up . Because of this, every actor in a program use react instead of receive. 


import scala.actors.Actor
import scala.actors.Actor._

// Because react doe not return, the message handler you pass it must now both process that message and arrange to do all the actor's remaining work. 
//Writing an actor to use react instead of receive challenging, but pays off in performance. Because react does not return, the calling actor's call sack can be discarded. 
object NameResolver extends Actor { 
  import java.net.{InetAddress, UnknownHostException}
  def act() { 
   react {
	    case (name : String, actor : Actor) => 
	      actor ! getIp(name)
	      act()
	    case "EXIT" => 
	      println("Name resolver existing.")
	      // quit, because there is not call to act() again 
	    case msg => 
	      println("Unhandled message :" + msg)
	      act()
    }
   }
  
  def getIp(name: String) : Option[InetAddress] = { 
    try { 
      Some(InetAddress.getByName(name))
    } catch { 
      case _ : UnknownHostException => None
    }
  }
}
As you can see, you have to call the work method again in the act method, so that you can continously receive messages. 



import scala.actors.Actor
import scala.actors.Actor._
NameResolver.start()

NameResolver ! ("www.scala-lang.org", self)

self.receiveWithin(100) { case x => x }

NameResolver ! ("wwwwwww.scala-lang.org", self)

self.receiveWithin(100) { case x => x}
The coding is so common with event-based actors, there is a special support in the library for it. The Actor.loop function exxecutess a block of code repeatedly, even if the code called react, NameResolver's act method can be written to use loop as shwon below. 



// code pattern aboce is so comon with event-based actors, there is special support in the library for it. 
// the Actor.loop function executes a bloack of code repeatedly.

object NameResolver extends Actor { 
  import java.net.{InetAddress, UnknownHostException}
  def act() { 
    loop { 
      react { 
        case (name : String, actor : Actor)  => 
          actor ! getIp(name)
        case msg => 
          println("Unhandled message : " + msg )
      }
    }
   }
  
  def getIp(name: String) : Option[InetAddress] = { 
    try { 
      Some(InetAddress.getByName(name))
    } catch { 
      case _ : UnknownHostException => None
    }
  }
}

Good actors style 


Actor should not block

Block on a actor may result in a deadlock , image actor A block on something, while another actor may request A to complete some work, that may result in dead-lock. 

Intead of blocking, the actor should arrange for some messages to arrive designating that action is ready to be taken. Often this rearrangement will require the help of other actors. For example, insteading of calling Thread.sleep directly and blocking the current actor, you could create a helper actor that sleeps and then sends a message back when enough time has elapsed. 


actor {
  thread.sleep(time)
  mainActor ! "WAKEUP"
}
and with the techniques, let's see an example where two actors collaborate. 



val sillyActor2 = actor { 
  def emoteLater() {
    val mainActor = self
    actor {
      Thread.sleep(100)
      mainActor ! "Emote"
    }
  }
  
  var emoted = 0
  
  emoteLater ()
  
  loop { 
    react { 
      case "Emote" => 
        println("I am acting!")
        emoted += 1
        if (emoted < 5)emoteLater() 
      case msg => 
        println("received: " + msg )
    }
  }
}

sillyActor2 ! "hi there"

Communicate with actors only via messages


the key way the actors model address the difficulties of the shared data and locks model is by providing a safe space. 
DO NOT use shared data, as long as you use shared data, you drop back down into the shared data and locks model.

A special NOTE: how does the react works.

the return type of Nothing indicates a function will never return normally, but instead it will always abruptly with an exception.
when you call start on an actor, the start method will in some way arrange things such that some thread will eventually call act on that actor. if the act method invoke react, the react mehtod will look in
the actor's mailbox for a message that passed partial function can handle, (it does this the same way as receive,  by passing candidate messages to the partial funciton's isDefinedAt method.) if it find amessage 
to the partial function that can be handled, react will schedule the handling of that messages for later execution and throw an exception.  if it does not find one, it will be put the actor in "cold storage". 
to get resurrected if and when it gets more messages in its mailbox. 
the thread that invoked act will be catch the exception, forget about the actor, and move to other duties. 

Prefer immuatble messages 


Because Scala's actors model provides what amount to a single-threaded environment inside each actor's act method.  You need not worry about whether the objects you can use in the implementation of this method are thread-safe. 

This is why the actors model  is called a shared-nothing model - the data is confined to one thread rather than being shared by many. 

There is one exception to the shared-nothing rule, however,: the data inside objects used to send messages between actors is "shared" by multiple actors. As a result, you do have to wrorry about whether messages objects are threads safe, And in general , they should be. 

because Scala's actor model provides what amounts to a single-threaded environment inside each actor's act method, you need not worry about whether the objects you use in the implementation of this method are thread-safe, you can use unsynchronized, mutable object to your hearts contents in an act method. 

there is on exception to the shared-nothing rule, however: the data inside objects used to send messages between actors is "shared" by multiple actors, as a result, you do have to worry about whether message object are3 thread-safe, and in general, they should be. 

  1. if you use mutable, make sure every unsynchronized, mutable object is "owned" and therefore accessed by, only one actor. 
  2. for list, or other collections, return a arr.clone or arr.toList which makes a immutable list. 

Make messages self-contained

like below, not only the name, but also the ip address (suppose that you wan to make a requeset by that ip address) 

// redudancy has it advantage!
def act() {
  loop { 
    react { 
      case (name : String, actor : Actor ) => 
        actor ! (name, getIp(name))
    }
  }
}
another way to increase redundancy in the message is to make a case class for each messages. 



// another way to increase redundancy in the messages  is to make a case class for each of messages. 
import scala.actors.Actor._
import scala.actors.Actor
import java.net.{InetAddress, UnknownHostException}

case class LookupIP(name : String, respondTo : Actor) 
case class LookupResult (name : String, 
    address : Option[InetAddress]
)

object NameResolver2 extends Actor { 
  def act() {
    loop { 
	    react {
	      case LookupIP(name, actor)  => 
	        {
	          println("Request on LookupIP(name, actor")
	          println(actor)
	          actor ! LookupResult (name, getIp(name))
	          println("returned result to the actor")
	        }
	    }
    }
  }
  
  def getIp(name : String) : Option[InetAddress] = { 
    try {
      Some(InetAddress.getByName(name))
    } catch  {
      case _ : UnknownHostException => None
    }
  }
}
and then feed this to the following example



NameResolver2.start()
NameResolver2 ! LookupIP("wwww.scala-lang.org", self)
self.receiveWithin(100) {
  case LookupResult (name, Some(ip)) => name 
  case x => x
}


Longer example : Parallel discrete event simulation 

in this section, we wilil create a simulatin with the actor library, the simultaito which we have done in stateful object chpater, but we will convert it to multi-actor paradigm..

Overall design is that we have a centralized clock, and we have one abstract class called Simulant that is the base class for Wire and Gate. Wire connect Gate, are inputs/outputs to gates, gate receives singal from wires, does signal logics and send it to output wire. Clock commuicate with Simulants with two messages, one is ping, and one is pong, ping is from Clock to simulant, to pulse the simulant to make action, whlle the pongs are response from the simulants. Clock advance its current time and process messages that has value less or equal than the current timestamp. Each simulant event is passed as a WorkItem managed by the clock.  Since gate calculate output with a gate delay, the Gate may need to queue next action (change the wire and notify other gates). We should also have a Circuit class where the gates and wires are assembled. We need additional event to inform that the wire has changed and additional event to start the gate computing the signals. To help debugging, we need to have some probing mechanism/machinery.

check the overall design. 

// check on the overall design of the simulants. 
// 
// Overall designs
/*import scala.actors.Actor
import scala.actors.Actor._

trait Simulant extends Actor 

class Wire extends Simulant

// we need a centralized coordinator
class Clock extends Actor {
  private var running = false
  private var currentTime = 0
  private var agenda : List[WorkItem] = List()
}*/
and now is the overal code. 
import scala.actors.Actor
import scala.actors.Actor._
// the ping and Pong message
case class Ping(time: Int)
case class Pong(time: Int, from: Actor)

// Coordinator class
case class WorkItem(time: Int, msg: Any, target: Actor)

case class AfterDelay(delay: Int, msg: Any, target: Actor)

// last signal object 
case object Start
case object Stop

// Implementing the simulation framework 
class Clock extends Actor {
  private var running = false
  private var currentTime = 0
  private var agenda: List[WorkItem] = List()
  private var allSimulants: List[Actor] = List()
  private var busySimulants: Set[Actor] = Set.empty

  //  def start() {}

  def add(sim: Simulant) {
    allSimulants = sim :: allSimulants
  }

  def act() {
    loop {
      if (running && busySimulants.isEmpty)
        advance()

      reactToOneMessage()
    }
  }

  def advance() {
    if (agenda.isEmpty && currentTime > 0) {
      println("** Agenda empty. Clock exiting at time " + currentTime + ".")
      self ! Stop
      return
    }

    currentTime += 1
    println("Advancing to time " + currentTime)
    processCurrentEvents()
    for (sim <- allSimulants)
      sim ! Ping(currentTime)
    busySimulants = Set.empty ++ allSimulants
  }

  private def processCurrentEvents() {
    val todoNow = agenda.takeWhile(_.time <= currentTime)
    agenda = agenda.drop(todoNow.length)
    for (WorkItem(time, msg, target) <- todoNow) {
      assert(time == currentTime)
      target ! msg
    }
  }

  def reactToOneMessage() {
    react {
      case AfterDelay(delay, msg, target) =>
        val item = WorkItem(currentTime + delay, msg, target)
        agenda = insert(agenda, item)
      case Pong(time, sim) =>
        assert(time == currentTime)
        assert(busySimulants contains sim)
        busySimulants -= sim
      case Start => running = true

      case Stop =>
        for (sim <- allSimulants)
          sim ! Stop
        exit()
    }
  }

  //  def act() { 
  //    loop {
  //      react { 
  //        case Stop => exit()
  //        case Ping(time) => 
  //          if (time == 1) simStarting()
  //          clock ! Pong(time, self)
  //        case msg => handleSimMessage(msg)
  //      }
  //    }
  //  }

  def insert(agenda: List[WorkItem], item: WorkItem): List[WorkItem] = {
    if (agenda.isEmpty || item.time < agenda.head.time) item :: agenda
    else agenda.head :: insert(agenda.tail, item)
  }

  start()
}

trait Simulant extends Actor {
  // clock is also an abstract member 
  val clock: Clock
  // handleSimMessgae is an abstract method
  def handleSimMessage(msg: Any)
  def simStarting() {}
  def act() {
    loop {
      react {
        case Stop => exit()
        case Ping(time) =>
          if (time == 1) simStarting()
          clock ! Pong(time, self)
        case msg => handleSimMessage(msg)
      }
    }
  }
  // start the Actor
  start()
}

// Circuit
class Circuit {
  val clock = new Clock
  // Simulation messages
  // delay constants
  // Wire and Gate classes and methods
  // misc. utility methods. 

  val WireDelay = 1
  val InverterDelay = 2
  val OrGateDelay = 3
  val AndGateDelay = 3

  def start() { clock ! Start }

  // signal from gate to wire to change state
  case class SetSignal(sig: Boolean)
  // signal from wire to gate to change state
  case class SignalChanged(wire: Wire, sig: Boolean)

  class Wire(name: String, init: Boolean) extends Simulant {
    def this(name: String) { this(name, false) }
    def this() { this("unnamed") }
    // the Circuit.this.clock is an full-qualifier to differential the clock in the Circuit's central coordinator and the wire's clock...
    val clock = Circuit.this.clock

    clock.add(this)

    private var sigVal = init
    private var observers: List[Actor] = List()

    // why we don't have the override modifier
    def handleSimMessage(msg: Any) {
      msg match {
        case SetSignal(s) =>
          if (s != sigVal) {
            sigVal = s
            signalObservers()
          }
      }
    }

    def signalObservers() {
      for (obs <- observers)
        clock ! AfterDelay(
          WireDelay,
          SignalChanged(this, sigVal),
          obs)
    }

    override def simStarting() { signalObservers() }

    def addObserver(obs: Actor) {
      observers = obs :: observers
    }

    override def toString = "Wire(" + name + ")"
  }

  private object DummyWire extends Wire("dummy")

  abstract class Gate(in1: Wire, in2: Wire, out: Wire) extends Simulant {
    def computeOutput(s1: Boolean, s2: Boolean): Boolean

    val delay: Int

    val clock = Circuit.this.clock

    // this gate is part of the clock's simulants. 
    clock.add(this)
    
    // connect the gate to the wrie, we will make the gate as observers to its input.
    in1.addObserver(this)
    in2.addObserver(this)
    
    var s1, s2 = false

    // override the implementation from Simulants. 
    def handleSimMessage(msg: Any) {
      msg match {
        case SignalChanged(w, sig) =>
          if (w == in1)
            s1 = sig
          if (w == in2)
            s2 = sig
          clock ! AfterDelay(delay,
            SetSignal(computeOutput(s1, s2)),
            out)
      }
    }
  }

  // gates' utility method 
  def orGate(in1: Wire, in2: Wire, output: Wire) =
    new Gate(in1, in2, output) {
      val delay = OrGateDelay
      def computeOutput(s1: Boolean, s2: Boolean) = s1 || s2
    }

  def andGate(in1: Wire, in2: Wire, output: Wire) =
    new Gate(in1, in2, output) {
      val delay = AndGateDelay
      def computeOutput(s1: Boolean, s2: Boolean) = s1 && s2
    }

  def inverter(input: Wire, output: Wire) =
    new Gate(input, DummyWire, output) {
      val delay = InverterDelay
      def computeOutput(s1: Boolean, s2: Boolean) = !s1
    }

  // useful to add a wire-probe utility
  def probe(wire: Wire) = new Simulant {
    // the Circuit.this.clock is an full-qualifier to differential the clock in the Circuit's central coordinator and the wire's clock...
    val clock = Circuit.this.clock
    clock.add(this)
    wire.addObserver(this)
    def handleSimMessage(msg: Any) {
      msg match {
        case SignalChanged(w, s) =>
          println("signal " + w + " changed to " + s)
      }
    }
  }

}

trait Adders extends Circuit {

  def halfAdder(a: Wire, b: Wire, s: Wire, c: Wire) {
    val d, e = new Wire
    orGate(a, b, d)
    andGate(a, b, c)
    inverter(c, e)
    andGate(d, e, s)
  }

  def fullAdder(a: Wire, b: Wire, cin: Wire, sum: Wire, cout: Wire) {
    val s, c1, c2 = new Wire
    halfAdder(a, cin, s, c1)
    halfAdder(b, s, sum, c2)
    orGate(c1, c2, cout)
  }
}
we have combine some techniques from the chapter from the modularProgramming chapter. the beauty of the Circuite simulation is as follow. 
// an example on how you create and run the Simulations. 
//
// demo how you can use the the circuits ?
/*val circuit = new Circuit with Adders

// with the trait design, you can stack /mix in component which they plan to use. 
val circuit = new Circuit 
                with Adders
                with Multiplexers
                with FlipFlops
                with MultiCoreProcessors*/
To wireup every thing, here is what we get. 
object Demo {
  def main(args: Array[String]) {
    val circuit = new Circuit with Adders
    import circuit._

    val ain = new Wire("ain", true)
    val bin = new Wire("bin", false)
    val cin = new Wire("cin", true)
    val sout = new Wire("sout")
    val cout = new Wire("cout")

    // or you can 
    
    probe(ain)
    probe(bin)
    probe(cin)
    probe(sout)
    probe(cout)

    fullAdder(ain, bin, cin, sout, cout)
 
    circuit.start()
  }
}

Demo.main(Array[String]())



你可能感兴趣的:(scala)