5.2 Making actors event-based: react

Although event-based actors differ from thread-based actors in their waiting strategies, turning a thread-based actor into an event-based one is often straightforward. The thread-based actors we have seen so far used receive to wait for a matching message to arrive in their mailbox. To make an actor event-based, replace all uses of receive by invoking the react method. As with receive, react expects a block of message patterns that are associated with actions to process a matching message.

Although replacing receive with react is a simple code change, there are important differences in how receive and react can be used in programs. The following examples explore these differences.

    def buildChain(size: Int, next: Actor): Actor = {
      val a = actor {
        react {
          case 'Die =>
            val from = sender
            if (next != null) {
              next ! 'Die
              react {
                case 'Ack => from ! 'Ack
              }
            } else from ! 'Ack
        }
      }
      if (size > 0) buildChain(size - 1, a)
      else a
    }
Listing 5.1 - Building a chain of event-based actors.

Using react to wait for messages

Listing 5.1 shows the definition of a method that recursively builds a chain of actors and returns the first actor. Each actor in the chain uses react to wait for a 'Die message. When it receives such a message, the actor checks to see if it is last in the chain (in this case, next == null). The last actor in the chain simply responds with 'Ack to the sender of the 'Die message and terminates.

If the current actor is not the last in the chain, it sends a 'Die message to the next actor, and waits for an 'Ack message. When the 'Ack arrives, it notifies the original sender of the 'Die and terminates. Note that we store the sender of the original 'Die message in the local variable from, so that we can refer to this actor inside the nested react. Inside the nested react, sender refers to the next actor in the chain, whereas the current actor should send its 'Ack to the previous actor in the chain, which is stored in from.

    def main(args: Array[String]) {
      val numActors = args(0).toInt
      val start = System.currentTimeMillis
      buildChain(numActors, null) ! 'Die
      receive {
        case 'Ack =>
          val end = System.currentTimeMillis
          println("Took " + (end - start) + " ms")
      }
    }
Listing 5.2 - The main method.

Let's use the buildChain method by putting it into an object with the main method shown in Listing 5.2. We store the first command-line argument in the numActors variable to control the size of the actor chain. Just for fun, we take the time to see how long it takes to build and terminate a single chain of size numActors. After building the chain using buildChain, we immediately send a 'Die message to the first actor in the chain.

What happens is that each actor sends 'Die to the next actor, waiting for an 'Ack message. When the 'Ack is received, the actor propagates it to the previous actor and terminates; the first actor is the last one to receive its 'Ack. When the receive operation in the main method starts processing 'Ack, all actors in the chain have terminated.

Note that in the main method we cannot replace receive by react. The reason is that the main thread (the JVM thread executing the main method) should not terminate before it receives an 'Ack response. If you would use react, its message handler would be registered with the actor runtime, and then the main thread would terminate. When the main thread terminates, all other threads marked as daemons, including the threads of the actor runtime, are terminated, too. This means that the entire application would terminate! To avoid this problem, use react only inside an actor.

How many actors are too many?

Actors that use react for receiving messages are lightweight compared to normal JVM threads. Let's find out just how lightweight by creating chains of actors of ever increasing size until the JVM runs out of memory. Moreover, we can compare that chain with thread-based actors by replacing the two reacts with receives.

But first, how many event-based actors can we create? And how much time does it take to create them? On a test system, a chain of 1,000 actors is built and terminated in about 115 milliseconds, while creating and destroying a chain of 10,000 actors takes about 540 milliseconds. Building a chain with 500,000 actors takes 6,232 milliseconds, but one with 1 million actors takes a little longer: about 26 seconds without increasing the default heap size of the JVM (Java HotSpot Server VM 1.6.0).

Let's try this now with thread-based actors. Since we are going to create lots of threads, we should configure the actor runtime to avoid unreasonable overheads.

Configuring the actor runtime's thread pool

Since we intend to use lots of threads with thread-bound actors, it is more efficient to create those threads in advance. Moreover, we can adjust the size of the actor runtime's internal thread pool to optimize actor execution. Scala's actor runtime allows its thread pool to resize itself according to the number of actors blocked in receive (each of those actors needs its own thread), but that resizing may take a long time since the thread pool is not optimized to handle massive resizing efficiently.

We can configure the internal thread pool using two JVM properties, actors.corePoolSize and actors.maxPoolSize. The first property sets the number of pool threads that are started when the thread pool is initialized. The latter property specifies an upper bound on the total number of threads the thread pool will ever use. (The default is 256.)

To minimize the time it takes to resize the thread pool, we set both properties close to the actual number of threads that our application needs. For example, when running our chain example with 1,000 thread-based actors, setting actors.corePoolSize to 1,000 and actors.maxPoolSize to, say, 1,010 keeps the pool resizing overhead low.

With these settings in place, it takes about 12 seconds to create and destroy a chain of 1,000 thread-based actors. A chain of 2,000 threaded actors takes already more than 97 seconds. With a chain of 3,000 actors, the test JVM crashes with an java.lang.OutOfMemoryError.

As this simple performance test demonstrates, event-based actors are much more lightweight than thread-based actors. By running a large number of event-based actors on a small number of threads, the context-switching overhead and the resource consumption of thread-bound actors is reduced dramatically. The following sections explore how to program with event-based actors effectively.

Using react effectively

As we mentioned previously, with react an actor waits for a message in an event-based manner. Under the hood, instead of blocking the underlying worker thread, react's block of pattern-action pairs is registered as an event handler. The actor runtime then invokes that event handler when a matching message arrives.

The event handler is all that is retained before the actor goes to sleep. In particular, the call stack, as the current thread maintains it, is discarded when the actor suspends. This enables the runtime system to release the underlying worker thread, so that it can be reused to execute other actors.

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

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