Chapter 34

Actors

Back in chapter 21 we explored the concepts of multithreading and looked at both the foundation concepts as well as several easier ways to get our programs to utilize multiple threads. The reality of the situation is that programs need to be multithreaded to take full advantage of modern processors, even on mobile devices like tablets and smartphones. The addition of parallel collections in Scala 2.9 makes it remarkably easy to parallelize programs as long as they fit the model of having multiple pieces of data that can be operated on independently. If you code in a completely functional way with no mutable data, it is also possible to break work across many threads with little challenge.

Unfortunately, not all problems are well suited for functional approaches, nor is everything data parallel in the manner needed for parallel collections. The creators of Scala are well aware of the challenges of parallelism and they have worked to make available as many alternative solutions as possible, working under the assumption that there is no "silver bullet" to solve the problem.

In earlier chapters, anything that did not fit into the parallel collections model was dealt with by going down to Java libraries either in java.util.concurrent or all the way down to java.lang with the creation of individual threads and explicit synchronization. These approaches are difficult and error prone. For that reason, Scala includes one other model of parallelism that can help with problems that are not data parallel, the actor model.

34.1 Actor Model

The idea of the Actor model is that many problems can be solved by individual actors who work independently of one another and communicate through messages. A classic example of this would be an assembly line. Each worker on the line is an actor. Unlike the parallel collection model where each thread is doing roughly the same thing to different data, the workers on an assembly line can be doing very different things to the pieces they are working on. Those pieces get passed from one worker to the next going down the line to produce a final product. This mental image works fairly well for describing the actor model. Each actor can be working on its own, independent of the others, and doing a specialized task that perhaps only it does.

The actions of an actor are motivated by messages received from other actors. In the assembly-line analogy, a message could be an object that moves from one station to a bin by the next one or perhaps a shout giving people information. Either way, the message simply passes information from one actor to another. If that information comes in the form of a physical object, the actor that does the passing stops interacting with the physical object after passing it on.

The way the actor model works in threads and how it prevents standard problems like race conditions is dependent on the condition that when something is passed in a message, the sender stops changing it. You can picture each actor as having its own thread.1 Each actor can also have mutable data that it keeps privately. This is where the actor model differs from a pure functional approach. Mutability is allowed, but it needs to be internal to one actor at a time. Actors receive messages to act on instead of having methods called on them. The difference is subtle, but significant. A method call passes control of a thread from one object to another. A message that is sent from one actor to another does not give the receiving actor control over the thread of the sender. It is just information that is being sent, not thread control. This prevents race conditions and deadlocks because mutable data is localized to a single actor/thread.

A big part of the advantage of using actors, and why they are generally easier and less error prone than using thread level controls, is that you can think about your problem at a higher level. Instead of thinking about threads and synchronization, you approach the problem as having different actors that each work mostly independently and communicate with one another. Each actor can have a state and mutate that state. They each behave differently depending on the state that they are in. Indeed, you probably should not spend much time thinking about the fact that actors can be performing actions in parallel when you are working on an actor-based program. That part will simply fall out and multiple threads will automatically be used when it fits.

The actor model of parallelism is not unique to Scala. The Erlang programming language is probably the one most commonly associated with actors. Erlang is a functional language that does pretty much everything through actors. The Scala libraries even borrow from Erlang syntax so that sending messages to actors in Scala looks the same as it would in Erlang.

34.2 scala.actors Package

The standard actor implementation for Scala is found in the scala.actors package. This package was briefly mentioned in chapter 21 as a simple way to spawn threads with or without getting a return value using Futures.future or Actor.actor respectively. While the use of Actor.actor technically makes an actor, it has very limited functionality because the body is not in the scope of a new class that is a subtype of the Actor class. To make fully functional actors we need to create a new class that extends Actor.

The Actor type has one abstract method in it that must be implemented by all concrete subtypes, act():Unit. As the name implies, this method defines what the actor will do when it starts running. This is demonstrated in the following REPL sequence

scala> import actors._
import actors._


  
scala> val actor = new Actor {
 |	def act {
 |	 println("Hello world!")
 |	}
 |}
actor: java.lang.Object with scala.actors.Actor = $anon$1@368e091


  
scala> actor.start
res0: scala.actors.Actor = $anon$1@368e091


  
scala> Hello world!

Note that the print statement is not actually executed until the actor is told to start. When it is told to start, the line is printed using a separate thread from the REPL. In this case, that caused it to be printed after a prompt. What was done here is basically a longer version of this.

scala> Actor.actor{println("Hello world!")}
Hello world!
res1: scala.actors.Actor = scala.actors.Actor$$anon$1@7c7bdb49

When you call Actor.actor, the argument becomes the body of a new subtype of Actor and the start method is called on an instance of that type.

You can only call the start method once. Further calls to that method will not cause anything to happen. To make the act method occur again, the restart method can be called.

34.2.1 The receive Method and Messages

Used in the way shown above, an actor is little more than a separate thread for some action to take place in. To make more of the actor model we need to pass messages between actors and have the actors react to those messages. Messages are sent using an exclamation point using the form actor!message. The message has type Any so you can send anything that you wish as a message. There are some things, like case classes that work better for reasons that we will discuss here.

One way to have an actor get a message is by having it call receive. The receive method takes a single argument of a partial function. That partial function should have cases for the messages that the actor should handle with that receive. The fact that receive takes a partial function with cases is one of the first reasons why case classes make sense as messages. Anything that you can do pattern matching on makes sense when you are writing a partial function. This includes literals for numbers, strings, and tuples. The problem with using such built-in types is that the type itself does not carry meaning and this can lead to problems as an application grows. Using meaningful types can be just as important as using meaningful names for variables, methods, or types themselves.

Simple examples of actors are a bit lacking in their illustrative power. They are somewhat like little examples of object-orientation. They do not sufficiently motivate the topic. For that reason, we will instead look at pieces of code that are related to the larger example with project integration that will be fully fleshed out in section 34.3. The example code we will develop in that section uses actors to model elements in circuits at the level of logic gates. This will be put in the drawing program and use the Clickable trait like the DrawGraph did so that the user can build circuits graphically.

Computer circuits are built using the basic component of a transistor. Transistors can be connected in ways that produce the behavior of the logic operations that we have been using in logic operations since chapter 4: and, or, and not. These are commonly represented in graphical diagrams using the shapes shown in figure 34.1. The solid lines coming in and out of the gates represent wires that can be at a voltage that represents 0 or 1.

Figure 34.1

Figure showing the standard graphical representations of 'and', 'or', and 'not' gates.

These are the standard graphical representations of "and", "or", and "not" gates.

The simulation will use actors for the gates and wires. A basic implementation of a Wire class along with case classes for the types of messages that would be sent to the wires is shown below. The wire has a Boolean state for the voltage that is on it, values for the Cartesian position it should be drawn at, and a Buffer of the gates that it connects to.

 private class Wire(private[this] var state:Boolean,
  private[this] var x:Double,
  private[this] var y:Double) extends Actor{
 private[this] val outs = mutable.Buffer[Gate]()
 def act {
  while(true) {
  receive {
   case SetWire(v) =>
   if(v!=state) {
    state = v
    outs foreach (_ ! WireChange(state,this))
    drawing.refresh
   }
   case MoveTo(nx,ny) =>
   x = nx
   y = ny
   case GetPos => sender ! (x,y)
   case GetPosAndState => sender ! PositionAndState(x,y,state)
   case ConnectWireOutput(g) =>
   outs += g
   g ! WireChange(state,this)
   case DisconnectOut(g) => outs -= g
 }
 }
}
}
private case class SetWire(value:Boolean)
private case class MoveTo(x:Double,y:Double)
private case class ConnectWireOutput(g:Gate)
private case class DisconnectOut(g:Gate)
private case object GetPos
private case object GetPosAndState
private case class PositionAndState(x:Double,y:Double,state:Boolean)

The only method in the Wire actor is act. This method follows something of a standard form for many actors when using receive. There is a while loop that goes until the actor is no longer needed with a call to receive inside of that. In this case the loop goes forever because the circuit is only supposed to stop running if the whole application is stopped. Such a loop is needed because each call to receive handles only a single message.

In this case, there are six different cases for six different types of messages that can be passed to a wire. The first sets the state. If there is a change, this change is passed on to all the connected gates. Note the use of ! to send a WireChange message to each gate. This is significant. Actors should only communicate using messages, not direct method calls. Direct method calls can lead to a breakdown of the safety of the actor approach with the race conditions that come with that. To make it extremely explicit that nothing other than the current actor can change the value of x, y, state, or what is stored in outs, all four member values are declared to be private[this]. This notation was briefly described in an aside on page 399. It means that those members can only be accessed by the object in which they exist. Not even other objects of the same type can see or change those values. All four of those values are mutable, which makes them potential causes of race conditions. When done properly, the actor model will limit modifications of such mutable values to a single thread. Declaring them private[this] helps to enforce the access requirements we want to maintain.

This goal of limiting access to a single thread is another reason for using case classes for messages. Data that is sent in a message is implicitly known to two different actors. If that data is mutable, it then becomes possible for two actors to make changes to the data and cause race conditions. The easiest way to prevent this is to use immutable data for messages. One of the features of case classes is that they are immutable. So if all your messages are case classes with immutable contents or basic immutable types like Int, Double, String, or tuples, you do not have to worry about two actors modifying the same data through a message.

While immutable messages are preferred because they keep life simpler, it is possible to use mutable messages. The assembly line example, if taken literally, is an example of mutable messages. The items that are passed from one station to another are the messages. If the workers are doing something to alter those items, then they are mutable. This situation can be replicated in code, but you have to make sure that when a mutable object is passed off, the actor sending the message stops modifying it. Ideally, it would erase all references to it just to be safe.

Two of the messages that are handled have code to send a message back to sender. The name sender, when used in this context, does exactly what you would expect. This is how we return a value from a message handler. The ! does not set up anything to deal with a response. It gives back a value of type Unit. For messages that need a response we can use one of two other sending operators. The !! operator returns a scala.actors.Future[Any]. This is the same Future type that we saw in section 21.5 for calls to Futures.future. There is an apply method that gives back the response, blocking if none is available. If you do not want to risk blocking, the isSet:Boolean method will tell you whether or not the value is ready. We could use this with a GetPos message with code like this.

val fut = comp !! GetPos
val (ccx:Double,ccy:Double) = fut()

The call to comp !! GetPos gives us back a Future[Any] that we call fut. The next line gets the value of that future and matches it to a tuple of Doubles. This is hard coded to a pattern with a tuple of Doubles because we feel secure that is what we will get as the response to a GetPos message. If we were worried about other possibilities it would be appropriate to use a match and have a case _ => option that gives back a default value instead of throwing a pattern match exception.

Futures can be really helpful when there are things that need to be done between the point where you start the calculation and when you want to use the result. This is not one of those situations. Here we automatically apply the future to get the value. This makes the code longer and forces the creation of a Future object that is not really needed. For this type of situation the !? operator is a preferable option. Unlike ! and !!, !? is blocking. So when you use it, the thread you use it in will block until the actor gets to the message and responds. This is exactly what happened above with the call to fut() so it is not a problem. Using this in our code segment simplifies it to this

val (ccx:Double,ccy:Double) = comp !? GetPos

One word of caution here, if you use !? and send a message to an actor that does not respond to it, the thread will block forever. It is basically like starting a calculation that includes an infinite loop.

34.2.2 react, loop, and loopWhile

The receive method works fine for many applications and would probably be fine for what we are doing here. However, it does not scale to programs with large numbers of actors. The reason is that receive blocks the actors thread until a message arrives that the partial function can deal with. Blocking the thread means that every actor that uses receive will need its own thread. There might be some actors for which you want to reserve a thread and then this is fine, or perhaps your total number of actors is small and having a thread for each is not a problem. If your applications needs thousands of actors or more, the overhead of having that many threads will be a huge drag on the application. Full threads simply have too much overhead.

The way to get around this is to use react instead of receive. The react method is called much like receive. You pass it a partial function that includes the handlers for the different messages that you want to deal with. Looking closely at the Application Programming Interface (API) entries will show you that there is a difference between the two.

def receive[R](f: PartialFunction[Any, R]): R
def react(handler: PartialFunction[Any, Unit]): Nothing

The receive method is parametric on the return type. You can use it as an expression and it will give you the return of the partial function when an appropriate message is received. The return type of react is Nothing. You might recall this type from figure 19.1 on page 453. It is at the very bottom of the Scala type hierarchy, a subtype of every other type. There is no instance of type Nothing, which means that a method of that type can not return. Indeed, that is the behavior of react.

When you call react, it never comes back. Anything that you put after the call to react will never happen. It is completely unreachable. The advantage of this is that when you use react in an actor, the actor library can take that thread and use it for something else before or after an appropriate message is arrives. If you have thousands of actors that all use react, they can do their work sharing a number of threads that is more appropriate for the machine that you are working on instead of being forced to give a thread to each and every actor.

Unfortunately, the fact that the method never returns causes a problem for the while loop idiom that was shown earlier. The while loop repeats in a normal thread. If you call react in a while loop, the partial function to handle the messages will only be called once, even if you tried to make an infinite loop with while(true). To deal with this problem, there are two other methods that are part of Actor, loop and loopWhile. If you are going to use react, you should probably use one of these. The while(true) idiom from above would be replaced with code like this.

  def act {
  loop {
  react {
   case ...
 }
 }
 }

The standard loop will cause the react to be executed until the application terminates. If you have a condition on which the actor should stop acting, you can use loopWhile as shown here.

  def act {
  var flag = true
  loopWhile(flag) {
  react {
   case ...
 }
 }
 }

Note that the syntax for loopWhile is identical to a standard while loop. This is possible thanks to three elements of the Scala syntax. First, both the condition and the body of the loop use pass-by-name semantics so that the code is executed multiple times. Second, the two arguments are passed separately using curried arguments. Third, Scala allows any argument list with only one argument to be surrounded by curly braces instead of parentheses. This is only done for the body here, but it gives the call the same structure as the loop that is part of the language syntax.

As with react, neither loop nor loopWhile return. If you put code after either of them it is unreachable and will never be executed.

Domain Specific Languages (DSLs)

Specialized problem areas can often benefit from having their own programming language to work with that has exactly the constructs that are needed for that area. The field of scientific computing has plenty of examples of whole languages developed primarily for solving specific types of problems. The languages built into tools like MATLAB®, Mathematica®, Maple®, and many others demonstrate this quite well. These Domain Specific Languages, DSLs, can make work in the field move much faster. However, creating a full language implementation is a difficult task that takes a lot of effort. Doing it well so that the resulting language is robust and has good performance is even harder.

Scala was designed to be highly scalable. One of the results of that effort is the ability to create libraries that look like they are language features and to create DSLs inside of Scala. The actor library is an example of this. Programming actors is programming Scala, but if your only goal was to develop actor parallel tools, you could learn a particular subset of Scala and the actor library and get up and running fairly quickly. The applications you write in this way will benefit from the effort that has gone into making Scala robust and get the performance of Scala at the same time. Writing DSL libraries in Scala takes a deep knowledge of the Scala language using advanced features that are not covered in detail in this book, but it is still a lot easier than creating your own complete language.

If you want a DSL that is dynamic, that you can nest inside of an application, and you do not care so much about performance, the combinator parsers that were discussed in chapter 30 can be helpful. As we saw in that chapter, if you can write a Context-Free (CF) grammar for a language, you can get a combinator parser up and running fairly quickly. That language can then allow your users to write simple logic inside of your running programs.

34.3 Circuit Simulation

As was mentioned earlier, we will integrate actors into the drawing framework using the example of a circuit simulation. The gates in a circuit have the ability to do logical operations on incoming voltages. Gates do not flip instantly though. When the potential on an input wire changes, there is a short delay before the output wire adjusts to the appropriate voltage. We could simulate this process using a discrete event simulation like those we discussed in chapter 26. While that is a good rigorous way of doing this, it is an inherently serial approach while circuits are really very parallel. The approach taken here uses actors to capture that parallelism.

This simulation is not going to be a highly efficient use of a multicore machine. That is largely because of the graphical updating. Instead, this is an example of using actors for event-based programming. Messages are passed between actors to represent events that occur in the circuit. For example, a wire is told when the gate that sets its voltage changes. That wire then sends messages to all the gates it is an input to about this new value. The goal here is to think about the circuit at the higher level of gates and wires and not worry about things like queues or threads.

The new DrawCircuit class will extend the Clickable trait that we first used with the DrawGraph. A fair bit of code is modeled after that class so that the user can add gates and switches, then connect wires by clicking on the drawing. There is a significant difference though. All values in the circuit components will be edited though messages. This includes the position of gates. In addition, the states of wires are queried using messages. This might seem like a small detail, but it makes the application more thread-safe. Recall that Graphical User Interface (GUI) events happen in a separate thread. When GUI events mutate values that are used by other threads, race conditions can occur. These race conditions are extremely hard to duplicate because they require user interaction. Actors give us a way of fixing a problem that you might not have even realized we had in code that was written earlier.

The code for DrawCircuit is shown below. At a bit over 400 lines it is one of the longest pieces of code we have seen in the book. Data representing the circuit structure is passed in and built into an array named comps. There is also a mutable Int that can be edited through the GUI named speedMult. This is a speed multiplier for the animation of the circuits. Smaller values shorten the delay between when a wire value changes and when the gates actually flip. There are also a number of different transient variables that are used for the graphical user interaction.

package scalabook.drawing


  
import java.awt.{Graphics2D,Color}
import java.awt.geom._
import scala.swing._
import scala.swing.event._
import actors.Actor
import collection.mutable


  
class DrawCircuit(p:DrawTransform,
 compData:Seq[DrawCircuit.CompData],
 wireData:Seq[DrawCircuit.WireData],
 private var speedMult:Int) extends DrawLeaf with Clickable {
  val parent = p
  private val comps = mutable.Buffer[CircuitComponent]()
  def setupComponents {
 import DrawCircuit._


  
 val gridLinks = for(cd <- compData) yield {
   val (cc,ins) = (cd match {
   case SwitchData(x,y,s) => (new Switch(s,x,y),Seq[Int]())
   case GateData(t,x,y,in) => t match {
    case "or" => (new OrGate(x,y),in)
    case "and" => (new AndGate(x,y),in)
    case "not" => (new NotGate(x,y),in)
  }
 })
  comps += cc
  cc.start
  (cc,ins)
 }
  for((cc,in) <- gridLinks)
  for(i <- in.indices; if in(i)>=0) {
   comps(in(i)) match {
   case w:Wire => cc ! ConnectGateInput(20∗i-10,w)
  }
 }
 }


  
  setupComponents


  
  @transient private var propPanel:Component = null
  @transient private var speedField:TextField = null
  @transient private var clickAction:(MouseEvent,Double,Double)=>Unit = null
  @transient private var hoverComp:CircuitComponent = null
  @transient private var startComp:CircuitComponent = null
  @transient private var (dx,dy) = (0.0, 0.0)
  @transient private var (sx,sy) = (0.0, 0.0)


  
  def draw(g:Graphics2D) {
  val posAndState = comps.map(_ !? GetPosAndState match {
  case pas@PositionAndState(x,y,s) => pas
 })


  
  def drawGate(gate:Gate) = {
  val (gx,gy) = gate !? GetPos match {
   case (x:Double,y:Double) => (x,y)
 }
  g.setPaint(if(gate==hoverComp) Color.red else Color.black)
  gate match {
   case og:OrGate =>
    val orPath = new GeneralPath()
    orPath.moveTo(-15+gx,-10+gy)
    orPath.quadTo(5+gx,-10+gy,15+gx,0+gy)
    orPath.quadTo(5+gx,10+gy,-15+gx,10+gy)
    orPath.quadTo(0+gx,0+gy,-15+gx,-10+gy)
    orPath.closePath()
    g.fill(orPath)
   case ag:AndGate =>
    g.fill(new Rectangle2D.Double(gx-15,gy-10,20,20))
    g.fill(new Arc2D.Double(gx-5,gy-10,20,20,-90,180,Arc2D.PIE))
   case ng:NotGate =>
    val triangle = new GeneralPath()
    triangle.moveTo(-15+gx,-10+gy)
    triangle.lineTo(10+gx,0+gy)
    triangle.lineTo(-15+gx,10+gy)
    triangle.closePath()
    g.fill(triangle)
    g.fill(new Ellipse2D.Double(gx+10,gy-2.5,5,5))
 }
  val (ins,outs) = gate !? GetWires match {
   case (i:List[Wire],o:List[Wire]) => (i,o)
 }
  for((iw,i) >- ins.zipWithIndex; if iw!=null) {
   val index = comps.indexOf(iw)
   g.setPaint(if(posAndState(index).state) Color.green else Color.black)
   g.draw(new Line2D.Double(posAndState(index).x, posAndState(index).y,
   gx-15, gy+20∗i-10))
 }
  for((ow,i) >- outs.zipWithIndex) {
   val index = comps.indexOf(ow)
   g.setPaint(if(posAndState(index).state) Color.green else Color.black)
   g.draw(new Line2D.Double(posAndState(index).x, posAndState(index).y,
   gx+15, gy+20∗i))
 }
  if(startComp!=null) {
   g.setPaint(Color.black)
   g.draw(new Line2D.Double(sx,sy,dx,dy))
}
 }


  
  def drawWire(i:Int) {
   val PositionAndState(x,y,s) = posAndState(i)
   g.setPaint(if(s) Color.green else Color.black)
   g.fill(new Ellipse2D.Double(x-3,y-3,6,6))
   if(comps(i)==hoverComp) {
   g.setPaint(Color.red)
   g.fill(new Ellipse2D.Double(x-2,y-2,4,4))
}
 }


  
  def drawSwitch(sw:Switch,i:Int) {
  val PositionAndState(x,y,s) = posAndState(i)
  g.setPaint(if(s) Color.green else Color.black)
  g.fill(new Rectangle2D.Double(x-4,y-4,8,8))
  if(sw==hoverComp) {
   g.setPaint(Color.red)
   g.fill(new Rectangle2D.Double(x-3,y-3,6,6))
}
  val wIndex = comps.indexOf(sw.out)
  g.setPaint(if(posAndState(wIndex).state) Color.green else Color.black)
  g.draw(new Line2D.Double(posAndState(wIndex).x, posAndState(wIndex).y, x, y))
}
 for((comp,i) <- comps.zipWithIndex) comp match {
  case g:Gate => drawGate(g)
  case w:Wire => drawWire(i)
  case s:Switch => drawSwitch(s,i)
}
}


  
 def propertiesPanel() : Component = {
 if(propPanel == null) {
  import BorderPanel.Position._
  propPanel = new BorderPanel {
   val radioButtons = Seq(
  new RadioButton {action = Action("Add Switch") {
   clickAction = (e,x,y) => e match {
   case mc:MouseClicked =>
    val sw = new Switch(false,x,y)
    comps += sw
    sw.start
   case _ =>
  }
 }},
  new RadioButton {action = Action("Add And Gate") {
   clickAction = (e,x,y) => e match {
   case mc:MouseClicked =>
    val and = new AndGate(x,y)
    comps += and
    and.start
   case _ =>
  }
 }},
  new RadioButton {action = Action("Add Or Gate") {
   clickAction = (e,x,y) => e match {
   case mc:MouseClicked =>
    val or = new OrGate(x,y)
    comps += or
    or.start
   case _ =>
  }
  }},
   new RadioButton {action = Action("Add Not Gate") {
  clickAction = (e,x,y) => e match {
   case mc:MouseClicked =>
   val not = new NotGate(x,y)
   comps += not
   not.start
   case _ =>
 }
 }},
  new RadioButton {action = Action("Connect Wire") {
  clickAction = (e,x,y) => e match {
   case mp:MousePressed =>
   if(hoverComp!=null) {
   startComp = hoverComp
   val (ccx,ccy) = hoverComp !? GetPos match {
    case (x:Double,y:Double) => (x,y)
    case _ => (0.0,0.0)
  }
   sx = ccx
   sy = ccy
   dx = ccx
   dy = ccy
  }
  case mp:MouseDragged =>
   if(startComp!=null) {
   dx = x
   dy = y
  }
  case mp:MouseReleased =>
   (startComp,hoverComp) match {
   case (null,_) =>
   case (_,null) =>
   case (w:Wire,g:Gate) =>
    g ! ConnectGateInput(y,w)
   case _ =>
 }
  startComp = null
   case _ =>
 }
 }},
  new RadioButton {action = Action("Move") {
  clickAction = (e,x,y) => e match {
  case mp:MouseDragged =>
    if(hoverComp!=null) hoverComp ! MoveTo(x,y)
  case mp:MouseReleased =>
    if(hoverComp!=null) hoverComp ! MoveTo(x,y)
  case _ =>
  }
 }},
  new RadioButton {action = Action("Flip Switch") {
  clickAction = (e,x,y) => e match {
   case mp:MouseClicked =>
    if(hoverComp!=null) {
   hoverComp match {
    case s:Switch => hoverComp ! FlipSwitch
    case _ =>
  }
   }
   case _ =>
 }
 }}
)
 val group = new ButtonGroup(radioButtons:_∗)
 layout += new GridPanel(radioButtons.length+1,1) {
  radioButtons.foreach(rb => contents += rb)
  speedField = new TextField(speedMult.toString) {
  listenTo(this)
  reactions += {
   case ed:EditDone => speedMult = text.toInt
 }
  }
   contents += speedField
 } -> North
}
 }
  propPanel
}
 override def toString() = "Circuit"


  
 def toXML : xml.Node = {
 <drawCircuit speed={speedMult.toString}>
  {comps.map(_.toXML)}
 </drawCircuit>
}


  
 def react(evt:Event,cx:Double,cy:Double) {
  evt match {
 case me:MouseEvent =>
  hoverComp = null
  var lastDist = 1e100
  for(comp <- comps) {
  val (ccx,ccy) = comp !? GetPos match {
   case (x:Double,y:Double) => (x,y)
 }
  val (dx,dy) = (cx-ccx, cy-ccy)
  val dist = math.sqrt(dx∗dx+dy∗dy)
  if(dist<15 && dist<lastDist) {
   hoverComp = comp
   lastDist = dist
 }
 }
  if(clickAction!=null) clickAction(me,cx,cy)
   drawing.refresh
  case _ =>
}
}


  
 private trait CircuitComponent extends Actor {
 def toXML:xml.Node
}


  
 private class Wire(private[this] var state:Boolean,
  private[this] var x:Double,
  private[this] var y:Double) extends CircuitComponent {
 private[this] val outs = mutable.Buffer[Gate]()
 def act {
  loop {
   react {
  case SetWire(v) =>
   if(v!=state) {
   state = v
   outs foreach (_ ! WireChange(state,this))
   drawing.refresh
  }
  case MoveTo(nx,ny) =>
   x = nx
   y = ny
  case GetPos => sender ! (x,y)
  case GetPosAndState => sender ! PositionAndState(x,y,state)
  case ConnectWireOutput(g) =>
   outs += g
   g ! WireChange(state,this)
  case DisconnectOut(g) => outs -= g
  }
 }
}
 def toXML:xml.Node = <wire index={comps.indexOf(Wire.this).toString}
  x={x.toString} y={y.toString} state={state.toString}/>
 }


  
  private abstract class Gate(inWires:Int,outWires:Int,
  private[this] var x:Double,
  private[this] var y:Double) extends CircuitComponent {
 def act {
  loop {
   react {
  case WireChange(v,w) =>
   Actor.actor {
   Thread.sleep(delay∗speedMult)
   for((iw,i) <- inputs.zipWithIndex; if iw==w) inputValues(i) = v
   setOutputs(inputValues,outputs)
  }
  case MoveTo(nx,ny) =>
   x = nx
   y = ny
  case GetPos => sender ! (x,y)
   case GetPosAndState => sender ! PositionAndState(x,y,false)
  case GetWires => sender ! (inputs.toList,outputs.toList)
  case ConnectGateInput(my,w) =>
   val index = (my-y+19).toInt/20 min inputs.length-1 max 0
   if(inputs(index)!=null) inputs(index) ! DisconnectOut(this)
   inputs(index) = w
   w ! ConnectWireOutput(this)
  }
 }
}
 def toXML:xml.Node = <comp type={gateType} x={x.toString} y={y.toString}>
  {inputs.map(g => <in>{comps.indexOf(g).toString}</in>)}
 </comp>
  private[this] val inputs = Array.fill(inWires)(null:Wire)
  private[this] val inputValues = Array.fill(inWires)(false)
  private[this] val outputs = Array.tabulate(outWires)(i => new
  Wire(false,x+25,y+20∗i))
  outputs.foreach(o => {o.start; comps += o})
  val delay:Int
  val gateType:String
  def setOutputs(ins:IndexedSeq[Boolean],outs:IndexedSeq[Wire]):Unit
}


  
 private class OrGate(x:Double,y:Double) extends Gate(2,1,x,y) {
  val delay = 20
  def setOutputs(ins:IndexedSeq[Boolean],outs:IndexedSeq[Wire]) {
 outs(0) ! SetWire(ins(0) || ins(1))
 }
  val gateType = "or"
}


  
 private class AndGate(x:Double,y:Double) extends Gate(2,1,x,y) {
  val delay = 15
  def setOutputs(ins:IndexedSeq[Boolean],outs:IndexedSeq[Wire]) {
 outs(0) ! SetWire(ins(0) && ins(1))
 }
  val gateType = "and"
}


  
private class NotGate(x:Double,y:Double) extends Gate(1,1,x,y) {
  val delay = 5
  def setOutputs(ins:IndexedSeq[Boolean],outs:IndexedSeq[Wire]) {
  outs(0) ! SetWire(!ins(0))
 }
  val gateType = "not"
}


  
private class Switch(private[this] var state:Boolean,
  private[this] var x:Double,
  private[this] var y:Double) extends CircuitComponent {
 val out = new Wire(state,x+20,y)
 out.start
 comps += out
  def act {
  loop {
   react {
  case FlipSwitch =>
    state = !state
    out ! SetWire(state)
    drawing.refresh
  case MoveTo(nx,ny) =>
    x = nx
    y = ny
  case GetPos => sender ! (x,y)
  case GetPosAndState => sender ! PositionAndState(x,y,state)
  }
 }
}
 def toXML:xml.Node = <comp type="switch" x={x.toString} y={y.toString}
   state={state.toString}/>
}


  
 private case class WireChange(value:Boolean,wire:Wire)
 private case class SetWire(value:Boolean)
 private case class MoveTo(x:Double,y:Double)
 private case class ConnectGateInput(y:Double,w:Wire)
 private case class ConnectWireOutput(g:Gate)
 private case class DisconnectOut(g:Gate)
 private case object GetPos
 private case object GetPosAndState
 private case object GetWires
 private case object FlipSwitch
 private case class PositionAndState(x:Double,y:Double,state:Boolean)
}


  
object DrawCircuit {
 def apply(p:DrawTransform) = new DrawCircuit(p,Seq(),Seq(),20)
 def apply(p:DrawTransform,n:xml.Node) = {
 val compData = (n  "comp").map(cn => {
  val x = (cn  "@x").text.toDouble
  val y = (cn  "@y").text.toDouble
  (cn  "@type").text match {
  case "switch" => SwitchData(x,y,(cn  "@state").text.toBoolean)
  case t => GateData(t, x, y, (cn  "in").map(_.text.toInt))
  }
})
 val wireData = (n  "wire").map(wn => {
  val x = (wn  "@x").text.toDouble
  val y = (wn  "@y").text.toDouble
  val index = (wn  "@index").text.toInt
  val state = (wn  "@state").text.toBoolean
  WireData(index,x,y,state)
})
 val speed = (n  "@speed").text.toInt
 new DrawCircuit(p,compData,wireData,speed)
}
  case class WireData(index:Int, x:Double, y:Double, state:Boolean)
  class CompData
  case class GateData(gType:String, x:Double, y:Double, ins:Seq[Int]) extends
   CompData
  case class SwitchData(x:Double, y:Double, state:Boolean) extends CompData
}

If you run this you can make some simple circuits like the ones shown in figure 34.2 then flip the switches and watch signals propagate through.

Figure 34.2

Figure showing screen shot of the drawing program with a DrawCircuit added and two different circuits. Each circuit has two switches on the left side represented by squares. Clicking a square can toggle them on or off. You can think of them as being inputs A and B. The bottom circuit is an 'exclusive or'. It implements the logic A xor B = (A or B) and not (A and B). The top circuit does not have an output. However, when both switches are turned on, a signal propagates around the loop.

This is a screen shot of the drawing program with a DrawCircuit added and two different circuits. Each circuit has two switches on the left side represented by squares. Clicking a square can toggle them on or off. You can think of them as being inputs A and B. The bottom circuit is an "exclusive or". It implements the logic A xor B = (A or B) and not (A and B). The top circuit does not have an output. However, when both switches are turned on, a signal propagates around the loop.

The elements of this code that are new center on the use of actors. Instead of calling methods on any element of comps, messages are passed. When values are needed immediately for things like drawing, the message is sent with !?.2

In addition to the Wire type there is a Gate with several subtypes and a Switch that are also subtypes of CircuitComponent. Each of the Gate subtypes has to define a delay value. This is proportional to how long it will take between the time an input wire changes and when the output wire changes. Note that the WireChange message handling in the Gate class includes a call to Thread.sleep to affect the delay. Because of this delay, and the desire to not block the actor thread, this was put in a call to Actor.actor so that it would occur in a separate thread and allow the actor to handle other messages during that time.

The way the actor message handling is written here should almost make it look like methods. The case class names resemble method names and the variables that are bound in the pattern are much like the arguments to a method. What follows that is the body. The message passing approach is more dynamic than normal method calls. This can have some benefits, but it has the downside that the compiler will not find as many errors for you. Sending a message to an actor that does not respond to that message type is not an error. If you use ! or !! to send the message it will not even cause a runtime error because messages that are not dealt with simply sit in the actors message queue. This also means a lot more match expressions in the code in places where inclusion polymorphism and subtyping would have been employed using standard approaches.

There is one other, very significant aspect of this code that should be noted. As it is written, the DrawCircuit type does not serialize. This means that drawings that include DrawCircuit can not be saved to binary or zip, nor can they be used in network collaborations as those features rely on object streams and serialization. The reason for this is that the Actor type is not serializable. More specifically, the message queue of an actor can not be serialized. Even if we make CircuitComponent extends Serializable, attempts to serialize will still fail. We could get around this by making comps transient and overriding the default serialization. Given the current length of this code, adding more for that purpose was not seen as a good idea. The XML support for saving and loading is present so that drawings with circuits can be saved and do not have to be redrawn every time.

Akka

The actors discussed in this chapter use a library that comes with Scala in the standard libraries. This library is fairly simple to use and can meet most demands. There is also a scala.actors.remote package that was not discussed here that allows you to have actors distributed across multiple machines. Despite all of this, there is another actor library written for Scala that is recommended for really large actor-based projects or ones where performance is critical. This is the Akka library, http://akka.io/.

Akka is viewed as one of the primary extensions to the base Scala install, as signified by its addition to the Typesafe stack, http://typesafe.com/. Akka actors have a lower overhead than Scala actors by virtue of not use the same mailbox type of structure of messages. It is also designed to be fault tolerant so that when things go wrong, the program will continue running. Support for remote actors is present in Akka as well for applications that need to scale beyond a single computer.

34.4 End of Chapter Material

34.4.1 Summary of Concepts

  • The actor model is a different approach to concurrency and parallelism in programming.
    • You can imagine each actor at having its own thread.
    • Actors communicate by sending messages, not calling methods.
    • Actors can have mutable data. As long as they do not share mutable data there is no chance for race conditions.
  • Scala comes with support for actors in the scala.actors package.
    • To create fully functional actors, make classes that extend the Actor trait.
    • Those classes should implement the act method. This is what is called when an actor is started.
    • To handle messages, the act method should include a call to receive or react, typically inside of a looping structure.
      • Both receive and react take a single argument that is a partial function. It should have cases for the different messages that actor should be sent and appropriate handling code.
      • The receive method if a standard method that blocks until an appropriate message is sent to that actor. It holds onto the actors thread and returns a value so it can be used as an expression.
      • The react method does not return. Anything after it is unreachable. This allows thread sharing among actors. You need to use loop or loopWhile to make it repeat. A simple while loop will not work.
    • It is ideal for messages to be immutable and work with pattern matching. For this reason, case classes are often used to create different types of messages.
  • An example of an actor-based event simulation was written for circuits at the logic gate level.

34.4.2 Exercises

  1. Write a simple actor that uses receive without a loop. Start the actor and verify that it will only handle a single message without a restart.
  2. Take your actor from the previous exercise and put the call to receive in a while loop then verify that it can now handle multiple messages.
  3. Write an actor that has the ability to know about another actor of that same type that can cooperatively count down. The actor should handle two messages. One will pass it an actor that will be its friend. The other handles a numeric value and if it is greater than zero it prints the value and then sends a message tot he friend with a value one less than what it printed. Make two of these actors and have them count down. To verify that both actors are involved in the conversation, you can give them names that are printed along with the numeric values.
  4. Use the actor you wrote for the last exercise with the name extension and have 3 or 4 of them arranged so that they count down in a loop.
  5. Edit the code for the graphical display of circuits in this chapter to clean it up and make it look better.
  6. Look up a 4-bit adder and build one in the circuit framework. Watch it work as you change input switches.
  7. Make a new Gate type with 8 inputs and 8 outputs for a full byte adder.
  8. Edit the code to make the circuit simulation from this chapter serializable using default serialization.

34.4.3 Projects

Any of the projects can be reworked so that they employ actor parallelism. The descriptions here list some different approaches you might consider using, based on which project you have been developing.

  1. In the MUD project, it is possible to convert all of the major mutable entities in the game over to use actors. This includes not only characters, but rooms as well. All actions that occur in the MUD turn into messages. The advantage of doing this is that players can have their actions happen in parallel and the system will scale to take advantage of many threads, without you, as the programmer, having to worry about synchronization on the mutable data.

    There should also be an actor that acts as a timer for actions that are not motivated by the players. That actor could include a priority queue of actions, or it could simply run through and send messages to all the various non-player characters telling them that they should perform their action. You have a bit of flexibility in the details. As long as you follow the general rule that you never allow two actors to have access to the same mutable data.

  2. The actor model can be used as an alternate way of organizing the processing in the web spider. You will have actor types for reading pages, parsing pages, storing pages, etc. You can also use actors for the queues for handling requests for new pages to work on.

  3. Graphical games can also be parallelized using the actor model. Here again you have to make certain that all mutable data in contained inside of actors that oversee access to them. One significant limitation in doing this for a graphical game is that the graphics themselves are still a single-threaded bottleneck. As such, the benefit you see will depend a lot on the style of the game and whether there is a significant amount of processing that happens outside of the graphics.

  4. Elements of the math worksheet can also be converted to actors to allow parallel processing. Some care must be taken to make sure that requests for information from different places do not lead to the revised system doing more work than is needed.

  5. All the mutable elements of a Photoshop® project can be converted to actors. Any filters or other processing that requires significant resources can also be moved into actors to take full advantage of parallelism.

  6. This chapter showed a simulation built on the concept of actors. If you have been writing the simulation workbench, you can put this or other simulations into place that are also based on actors.

1As we will see later in the chapter, you often do not want this extreme view to be the reality as there could be millions of actors and you do not really want to have millions of threads. Still, as the programmer will not be controlling the threads manually, the only safe assumption is that each actor could be acting on a separate thread.

2It should be noted that during the development of this code, that lead to the application hanging more than once when the !? operator was used with an actor that had not been started or did not respond to that message.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset