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.
Show you some of the troubles that we have in concurrently programming.
reason :
Shared data and synchronized sections, and locking mechanism.
problems:
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 actingYou 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 : 15You 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 nothingbut if you have this:
intActor ! 12 // an int printed out.
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!
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 } } }
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"
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.
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.
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 }
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]())