Friday 27 May 2011

How Heavy is Estragon - Event-Based Scala Actor

Dear Junior

As we have seen thread-based actors are quite an intuitive model, but is not very efficient in conserving resources. Its main drawback is that each actor needs a thread each, and each thread will take some memory - about 60-70kB.

Also, this amount of threads seem unnecessary as the thread is only used a potion of the time - so the threads could be pooled instead.

So, let us create the Estragon version - an actor that drowses off and takes a nap whenever he is not actively needed. Thus the thread can be used by some other actor that wants to be active at the moment.

class Estragon(id : Int) extends Actor {
  def threadid = {
    "T" + Thread.currentThread.getId
  }

  def name: String = {
    "Estragon" + id
  }

  def act() = {
    println(name + " is waiting " + threadid)
    react {
      case Godot =>
        println(name + " saw Godot arrive! " + threadid)
    }
    println(name + "'s wait is over " + threadid)
  }
}

In the code the difference from thread based Vladimir is really small - we use the method "react" instead of "receive". The rest of the API is the same: we subclass from Actor, we define an "act"-method. The only difference to get event-based actors (using a thread pool) instead of thread-based actors (having one thread each) is to switch from "receive" to "react".

Simple? Yes, until we get into details and subtleties later.

Now let's run the script for the play putting Estragon and Vladimir on stage and starting them.

object waitingforgodot extends Application {
  println("Setting the stage " +
    Thread.currentThread.getId)
  val estragon = new Estragon(1)
  val vladimir = new Vladimir(2)
  println("Starting the play")
  estragon.start
  vladimir.start
  println("Main script is over")
}


danbj$ scala godot.waitingforgodot
Setting the stage 1
Starting the play
Main script is over
Estragon1 is waiting T10
Vladimir2 is waiting T11
^C

Well not very exiting. Both Vladimir and Estragon start and enters into waiting-state, waiting for Godot. Only difference is their respective ways of waiting.

Vladimir keeps his thread, just putting it into a waiting state. Deep down under this is implemented through some "object.wait()". So whenever he gets out of his waiting state (when the message Godot finally arrive), the same thread can process the message-handler i e the receive-block.

Estragon on the other hand discards his thread when going into "react". As he has no immediate use of the thread, it is given back to the pool to serve some other actor that need to run. So whenever Godot arrives to Estragon the message-handler will be activated and run by some thread, not necessarily the same as earlier.

Nevertheless, the point is that when Estragon enters "react", the thread is no longer occupied but be used by other actors. So even if we have a lot of actors we still can manage with just a few threads.

This becomes more obvious if we create lots of Estragons

object estragongalore extends Application {
  override def main(args: Array[String]) {
    val actors = Integer.parseInt(args(0));
    val ids = 0 until (actors)
    val estragons = ids map (id => new Estragon(id))
    println((actors) + " actors on stage")
    for(estr <- estragons) { estr.start() }
  }
}

scala godot.estragongalore 10
10 actors on stage
Estragon0 is waiting T10
Estragon1 is waiting T11
Estragon3 is waiting T12
Estragon2 is waiting T13
Estragon5 is waiting T10
Estragon7 is waiting T10
Estragon8 is waiting T10
Estragon9 is waiting T10
Estragon6 is waiting T11
Estragon4 is waiting T12
^C

Estragon 0, 1, 2, and 3 where started in separate threads. But, the next actor to start (Estragon5) could use thread T10 that had been used by Estragon0 and returned to the pool. So to create 10 actors we only needed four actor threads.

That should conserve a lot of resources.

Remembering that my poor laptop cringed when we put 2500 Vladimirs on stage? Let us see how many Estragon we can put on stage. It ought to be more as this model reuses the threads. What about 10 000 actors?

danbj$ scala godot.estragongalore 10000 
10000 actors on stage
Estragon3 is waiting T12
Estragon2 is waiting T13
Estragon0 is waiting T10
Estragon1 is waiting T11
Estragon4 is waiting T11
…
Estragon9998 is waiting T12
Estragon9935 is waiting T11
Estragon9989 is waiting T13
Estragon9956 is waiting T10
Estragon9999 is waiting T12
^C
10 000? No problem. Let us tenfold that.
danbj$ scala godot.estragongalore 100000 
100000 actors on stage
Estragon0 is waiting T10
Estragon3 is waiting T13
Estragon2 is waiting T12
…
Estragon99991 is waiting T10
Estragon99999 is waiting T11
Estragon99998 is waiting T12
Estragon99996 is waiting T13
^C
100 000 worked fine. What about a million actors on stage?
danbj$ scala godot.estragongalore 1000000 
1000000 actors on stage
Estragon1 is waiting T13
Estragon0 is waiting T10
Estragon3 is waiting T12
…
Estragon163350 is waiting T10
Estragon163351 is waiting T10
Estragon163352 is waiting T10
java.lang.OutOfMemoryError: Java heap space
 at scala.concurrent.forkjoin.LinkedTransferQueue.xfer(LinkedTransferQueue.java:187)
 at ...
 at scala.actors.Scheduler$.execute(Scheduler.scala:21)
 at scala.actors.Reactor$class.dostart(Reactor.scala:222)
 at ...
 at godot.Estragon.start(waitingforgodot.scala:42)
 at ...
At last we got an OutOfMemoryError. This time we did not get it when trying to start a new thread, but somewhere in the scheduler instead. It turns out that 900 000 actors is just short of what a 256M heap can handle.
danbj$ scala godot.estragongalore 900000
900000 actors on stage
Estragon0 is waiting T11
Estragon2 is waiting T10
…

danbj$ ps -m -O rss
  PID    RSS   TT  STAT      TIME COMMAND
18186 345524 s001  S+     0:37.07 /usr/bin/java -Xmx256M …
RSS is "real memory" in kB and we note again that the scala startup script restricts heap to 256M per default. Let us plot memory use for some different number of actors.
actors    RSS
     1    61516
  1000    62044
  2500    61460
 10000   71452
100000  101588
900000  345524
So memory consumption is roughly 60MB in startup and 3kB per actor. That is a lot better than 60kB per actor for thread-based. That is pretty good. Now we can structure our systems using a lot of actors because the "overhead payload" of using an actor is not overwhelming. This is by the way an area where the framework Akka excels. In short: switching from thread-based actors to event-based actors is not more complicated than changing from "receive" to "react". Not at a syntactic code level at least.
However, we also change execution model. We are no longer guaranteed that our actor is run by the same thread. In fact, the thread that registers the message-handler by running "react" might not be the same thread that later runs the message-handler itself. And that might give us a clue to Estragon's bad memory. Yours Dan

ps Estragons bad memory might be explained if we dissect how the two execution models work.

Wednesday 18 May 2011

Vladimir Galore - Lots of Threaded Scala Actors Waiting for Godot

Dear Junior

When we discussed the model for programming actors in Scala we saw that the thread-based model could be compared to the very actively waiting character Vladimir of Waiting for Godot. I also mentioned that the more drowsy, laid-back Estragon of the same play could be a good picture of the other model, the event-based.

However, before proceeding to the event-based actors, I think it enlightening to dive a bit more into the thread-based and see what the problem is.

The point of actor-based programming is that you want to mimic a "society of collaboration" where each actor performs a focused task. And you do not just want one actor per function (or type of task), you actually want one actor per task. So you want a lot of them.

This is similar to object orientation - you do not want one object per class (e g representing phone numbers), you want one object per instance of the class (one for each phone number). Actually, it can be claimed that the restricted actor-message model is closer to the original ideas of object orientation than the object-method model we have become accustomed to.

So we really want loads of actors. Let us see how many Vladimirs we can create before my poor laptop cringes.

Let us revise the code for Vladimir in his waiting for Godot. As we will create a lot of Vladimirs I have given each an id.

class Vladimir(id : Int) extends Actor {
  def threadid = {
    Thread.currentThread.getId
  }

  def name: String = {
    "Vladimir" + id
  }

  def act() = {
    println(name + " is waiting " + threadid)
    receive {
      case Godot =>
        println(name + " saw Godot arrive! " + threadid)
    }
    println(name + "'s wait is over " + threadid)
  }
}

case class Godot

Now we also need to create loads of them. Let us make a list of integers and turn each of them into an instance of Vladimir.

object vladimirgalore extends Application {
  override def main(args: Array[String]) {
    val actors = Integer.parseInt(args(0));
    val ids = 0 until actors // [0,1,2 ...]
// turn each int to an actor using the int as id
    val vladimirs = ids map (id => new Vladimir(id))
    println(actors + " actors on stage")
    for(vlad <- vladimirs) { vlad.start }
  }
}

scala godot.vladimirgalore 6
6 actors on stage
Vladimir2 is waiting 12
Vladimir0 is waiting 10
Vladimir1 is waiting 11
Vladimir3 is waiting 13
Vladimir4 is waiting 17
Vladimir5 is waiting 18
^C

Never mind the order of the output - actors are threads and are thus entitled to run scheduled in any order. What we see is six actors, each an instance of Vladimir, and each given a thread of its own. And every one of them are in a wait-state waiting for the message "Godot". Let us relieve one of them from its wait, just to see one completion. Let us send Vladimir4 the happy message of Godot's arrival.

object vladimirgalore extends Application {
  override def main(args: Array[String]) {
    val actors = Integer.parseInt(args(0));
    val ids = 0 until (actors)
    val vladimirs = ids map (id => new Vladimir(id))
    println(actors + " actors on stage")
    for(vlad <- vladimirs) { vlad.start }
    vladimirs(4) ! Godot
  }
}

danbj$ scala godot.vladimirgalore 6
6 actors on stage
Vladimir1 is waiting 11
Vladimir2 is waiting 12
Vladimir3 is waiting 13
Vladimir0 is waiting 10
Vladimir4 is waiting 17
Vladimir4 saw Godot arrive! 17
Vladimir4's wait is over 17
Vladimir5 is waiting 17
^C

Ahaa. Vladimir 4 was started with thread 17 - which used "receive" to register the message handler (the code block containing the "case"). It was also thread 17 that later executed the message handler, doing the pattern matching of the case and running the corresponding code for "case Godot". Finally it was thread 17 that continued the code after the handler-block. Same thread all the way - that is why they are called thread-based. They do not only behave as if they where a thread, they are actually implemented using the same thread all the time.

Accidentially, thread 17 managed to complete the act-method of Vladimir4 so that specific thread could be reused for Vladimir5. However, in all other cases, a new fresh thread was required.

Now let us put loads of actors on stage. For clarity we remove the release of Vladimir 4 so that every actor will be waiting and all threads be locked up.

Let us see if we can put 1000 Vladimirs on stage and set them acting.


object vladimirgalore extends Application {
  override def main(args: Array[String]) {
    val actors = Integer.parseInt(args(0));
    val ids = 0 until (actors)
    val vladimirs = ids map (id => new Vladimir(id))
    println(actors + " actors on stage")
    for(vlad <- vladimirs) { vlad.start }
    // vladimirs(4) ! Godot
  }
}

danbj$ scala godot.vladimirgalore 1000
1000 actors on stage
Vladimir0 is waiting 10
Vladimir3 is waiting 13
Vladimir2 is waiting 12
Vladimir1 is waiting 11
Vladimir4 is waiting 16
Vladimir5 is waiting 17
…
Vladimir115 is waiting 128
Vladimir116 is waiting 129
Vladimir117 is waiting 130
Vladimir118 is waiting 131
Vladimir119 is waiting 132
…
Vladimir251 is waiting 264
Vladimir252 is waiting 265
Vladimir253 is waiting 266
Vladimir254 is waiting 267
Vladimir255 is waiting 268
^C

So, the system hangs on Vladimir255 even though it has not yet started all the 1000 actors I asked for. 

Hmm … "Vladimir0" to Vladimir255" - that is 256 actors that have been started before the system hangs. Such a number is hardly a coincidence … Here my colleague George Spalding came to the rescue by pointing out the relevant JVM properties, in this case "actors.maxPoolSize (default 256)". So, as I understand it Scala will not allow the JVM to allocate more than 256 threads for actor stuff. This means that when 256 Vladimirs had been started, then all 256 threads where sitting waiting for receiving Godot.


receive {
      case Godot =>
        println(name + " saw Godot arrive! " + threadid)
    }

And if the runtime-system refuse to allocate more threads, then no more actors will be started.

Let us run it again, with modified properties, increasing the maximum number of actor threads.


scala -Dactors.maxPoolSize=10000 godot.vladimirgalore 1000
1000 actors on stage
Vladimir0 is waiting 10
Vladimir3 is waiting 13
Vladimir2 is waiting 12
Vladimir1 is waiting 11
Vladimir4 is waiting 16
Vladimir5 is waiting 18
…
Vladimir997 is waiting 1010
Vladimir998 is waiting 1011
Vladimir999 is waiting 1012
^C

Ok now it worked… what about 2500?


danbj$ scala -Dactors.maxPoolSize=10000 godot.vladimirgalore 2500
2500 actors on stage
Vladimir0 is waiting 10
Vladimir3 is waiting 13
…
Vladimir2498 is waiting 2511
Vladimir2499 is waiting 2512
^C

Seems to work … and 5000?


danbj$ scala -Dactors.maxPoolSize=10000 godot.vladimirgalor 5000
...
Vladimir2538 is waiting 2552
Vladimir2539 is waiting 2553
godot.Vladimir@35a631cc: caught java.lang.OutOfMemoryError: unable to create new native thread
java.lang.OutOfMemoryError: unable to create new native thread
 at java.lang.Thread.start0(Native Method)
 at java.lang.Thread.start(Thread.java:658)
 at scala.concurrent.forkjoin.ForkJoinPool.createAndStartSpare(ForkJoinPool.java:1609)
 at ...
 at scala.actors.Scheduler$.managedBlock(Scheduler.scala:21)
 at scala.actors.Actor$class.receive(Actor.scala:512)
 at godot.Vladimir.receive(waitingforgodot.scala:38)
 at godot.Vladimir.act(waitingforgodot.scala:49)
 at ...
 at scala.actors.ReactorTask.run(ReactorTask.scala:36)
 at ...
 at scala.concurrent.forkjoin.ForkJoinWorkerThread.mainLoop(ForkJoinWorkerThread.java:340)
 at ...
Vladimir2540 is waiting 2553
^C

Nope it crashed.

But … systems always reveals interesting information when breaking down.

In this case it was the scala.actors.Scheduler that tried to start a new Thread in order to serve the Vladimir "receive" inside its act-method. And creating this "new native thread" was too much load for the JVM that crashed with OutOfMemoryError.

Let us run this once again, just below the limit where it crashes.

danbj$ scala -Dactors.maxPoolSize=10000 godot.vladimirgalor 2539

and have a look at process status


danbj$ ps -m -O rss
  PID    RSS   TT  STAT      TIME COMMAND
 8164 264280 s001  R+     0:06.80 /usr/bin/java -Xmx256M …

OK, "RSS" stands for "resident set state" and is basically "real memory". So, memory use is 264280 kB. Also note that the scala runtime has set the JVM "maximum heap size" (Xmx) to 256M. These two numbers are strikingly close to each other. My conclusion is that it seems to be the allocated heap that has filled up.

This does makes sense, a few thousand threads will need one allocated stack each, and that will eat the available space pretty quickly. Trying out with different number of actors give us some data on how much stack is needed per actor.

actors    RSS (in kB)
    1    62752
  500    95676
 1000   134132
 2500   239248

So, apart from that the system needs 62M just to start, every Vladimir (thread-based actor) seems to need just below 70k each. That is not very slim, not if we want to create truckloads of actors.

Obviously, the thread based actors have their advantage in being pretty easy to understand. But they definitely have their drawback in eating system resources by asking for one thread and a stack allocation each.

But, hey … in our benchmark all the threads where in wait-state. Could they not have been pooled in some way? We could have gotten away with just a few threads, and we would have been able to create many more actors!

Sure we could, and that is exactly what event-based actors do. Instead of being on their feet like Vladimir, we want them to drowse off and take a nap like Estragon. And in the mean-while the thread could be used by some other actor that has something in its inbox it wants to process. Unfortunately, they will also loose a little bit of their sense of time like him. But that will have to wait to another letter.

Yours

Dan

ps Check out what it looks like using the event-based execution model - with a very similar programming model.

Monday 16 May 2011

Scala Actor Waiting for Godot - Vladimir Thread Version

Dear Junior

My collegue Roozbeh Maadani and I where curious about the different actor models of Scala so we decided to try them out. I have also had a distant interest in Akka for a while, an interest re-vigored by Jonas Bonér's presentation at Jfokus, and deepened by attending his mini-course on Akka at OP-KoKo. So, I have been toying around with Akka a little as well. I will get back to that.

Let us get started and create an application with some actors - in this case an reenactment of Waiting for Godot. The basic plot of Waiting for Godot is roughly two characters, Vladimir and Estragon, waiting for a person named Godot to show up - which he does not. So let us start with the setup.

object waitingforgodot extends Application {
  println("Setting the stage")
  val vladimir = new Vladimir
  val estragon = new Estragon
}
class Estragon extends Actor {
  // here we will later read Estragon's script
  def act() = null
}
class Vladimir extends Actor {
  def act() = null
}

In the application script we create two actor objects, one "Vladimir" and one "Estragon", defined by one class each. When started (which they are not yet), they will run their respective scripts which is defined in their act()-method. Agreeably neither of them have much of a script to follow yet.

Nevertheless, we can run the application, even though the result is not very thrilling:

danbj$ scala godot.waitingforgodot
Setting the stage

Time to give one of the actors some script to follow. Let us start with Vladimir.

class Vladimir extends Actor {
   def act() = {
      println("Vladimir is waiting")
      // ... stuff happen
      println("Vladimir's wait is over")
   }
}

Running the app still gives

danbj$ scala godot.waitingforgodot
Setting the stage

Obviously, the script for Vladimir is not executed. This is because in the Scala "actors.Actor" API the basic metaphor is that an actor is a thread (conceptually). It is not necessarily implemented that way, but the API keeps the mental model as close to thread as possible. Thus, we need to start it.

object waitingforgodot extends Application {
   println("Setting the stage")
   val vladimir = new Vladimir
   val estragon = new Estragon
   println("Starting the play")
   vladimir.start
   estragon.start
   println("Main script is over")
}

Now we see the actor Vladimir acting his part as well

danbj$ scala godot.waitingforgodot
Setting the stage
Starting the play
Main script is over
Vladimir is waiting
Vladimir's wait is over

Note that the Vladimir script is actually running even after the main script is over. This is explain by "vladimir.start" is actually starting a new thread which keeps running even after the original main-thread has finished.

This becomes even more obvious if we add some thread information to our printlns.

object waitingforgodot extends Application {
   println("Setting the stage " + Thread.currentThread.getId)
   val vladimir = new Vladimir
   val estragon = new Estragon
   ...
}

class Vladimir extends Actor {
   def act() = {
      println("Vladimir is waiting " + 
         Thread.currentThread.getId)
      println("Vladimir's wait is over " + 
         Thread.currentThread.getId)
   }
}

Which yields

danbj$ scala godot.waitingforgodot
Setting the stage 1
Starting the play
Main script is over
Vladimir is waiting 10
Vladimir's wait is over 10

Main script is run by thread 1 and character Vladimir is run by thread 10. Curiously this actually apply to the play itself. In the play Vladimir is the only one who have a clear sense of time: he remembers the past (which the other characters does not, at least not very well), and he has an idea of the future to come.

Now let's get over to the point of the play, which is waiting for a specific person named Godot. Vladimir's script is extended by a "wait for Godot to arrive". The actor representation of this is to send a message to the actor Vladimir, a message consisting of "Godot".

Vladimir on his part must wait for this message to arrive in his "inbox". He does so by using "receive" and a message handler. Using "recieve" makes the actor to check its mailbox and act upon the messages it finds. The "message handler" is simply a codeblock that tells what to do with messages.

If there is no message in the inbox when the actor gets to "recieve", then the actor blocks and waits for a message to arrive.

class Vladimir extends Actor {
   def threadid = {
      Thread.currentThread.getId
   }

   def act() = {
      println("Vladimir is waiting " + threadid)
      receive {
         case Godot =>
            println("Vladimir saw Godot arrive! " + threadid)
      }
      println("Vladimir's wait is over " + threadid)
   }
}

In this code "receive" causes the thread to be put in a waiting-state and watch the actor's "inbox". The "inbox" is the way to communicate with an actor. You should neither access its data, nor should you call methods on the object directly. Instead you send "messages" to it, which it will receive and react upon. I think of it like corresponding with mail.

So, whenever a message arrive, the actor will run its message handler to see what it should do about it. The message handler in this case is the code-block following "receive"

receive {
   case Godot =>
      println("Vladimir saw Godot arrive! " + threadid)
}

So, if there is a message that matches the value "Godot", then it will be handled by running the code

println("Vladimir saw Godot arrive! " + threadid)

Let us ponder this for a while. The actor support in Scala is a library, not a part of the system. So "receive" is not a reserved word in the language, it is the name of a method of the Actor class (which we subclass). So, what actually is happening is that we execute the receive-method and pass in a code-block as the argument, the message handler.

So another way to phrase it is that we use "receive" to register a message-handler with the actor runtime (i e the thread). As the message-handler is passed as a parameter it will sit on the top of the stack. And there the processing stops (the thread goes to sleep).

At a later point of time (when there is message in the inbox), the processing will be resumed (by the same thread). Now, the message-handler is still there on the top of the stack so we can run it, do the case-pattern-matching, select the "println(...)" and execute it. Thereafter, the thread pops out of the "recieve {...}" and continue with the rest of the program.

So, the entire "receive, wait for message, pattern-match, run message handler" was just a slow subroutine call.

Now over to the message.

We have one piece missing: who (or what) is Godot?

In this example Godot is a simple value. There is no need to make it an actor, because it does not play any active part in the program. Actually, in the play there is no character Godot on stage at any time - he simply never shows up. So casting the Godot part is easy - there is no actor needed.

Thus we can represent Godot with a value object defined by a case class

case class Godot

Yepp, that is all that is needed to define a message.

This defines a class Godot which contains exactly one object: Godot (a little bit like an enum would). As there is no mutable state, this object can safely be shared by anyone interested in Godot, in the same way as there only need to be one integer 4711 or one string "fubar".

The main advantage of case classes is that they can be used for pattern matching, which is necessary if we want to receive them as messages the way we did.

Now let us run this.

danbj$ scala godot.waitingforgodot
Setting the stage 1
Starting the play
Main script is over
Vladimir is waiting 10
^C

This time I had to kill the process as there was one thread that had not finished. Thread 10 was still active running the Vladimir actor and fully occupied waiting for Godot. I e it was hanging in "receive" but as it never got "Godot" it kept hanging there.

This is by the way also a parallel to the play. In the play Vladimir is very energetic, at least compared to Estragon who also waits for Godot. Vladimir is on his feet all the time during the play, whereas Estragon often sits down and even takes a nap. Obviously, Estragon's strategy conserves resources better, but let us not hold that against Vladimir right now.

In the play, Godot never show up, but let us be nice to poor Vladimir just for a moment and send him a Godot to his inbox.

object waitingforgodot extends Application {
   println("Setting the stage " + Thread.currentThread.getId)
   val vladimir = new Vladimir
   val estragon = new Estragon
   println("Starting the play")
   vladimir.start
   estragon.start
   println("Let Godot arrive")
   vladimir ! Godot
   println("Main script is over")
}

Here we use the "!" (pronounced "bang") syntax for putting a message into the inbox of the actor referred by "vladimir".

This will wake vladimir from its recieve-wait-state.

receive {
   case Godot =>
      println("Vladimir saw Godot arrive! " + threadid)
}

Now that actor is reactivated, it will pattern-match the "case" clauses (only one in this example) in the handler and run the corresponding code.

danbj$ scala godot.waitingforgodot
Setting the stage 1
Starting the play
Let Godot arrive
Vladimir is waiting 11
Main script is over
Vladimir saw Godot arrive! 11
Vladimir's wait is over 11 

This time I did not need to kill the process as the Vladimir thread had a chance to finish its execution.

Note also that messages are sent asynchronously so there is no guarantee that message handling is done immediately after message is sent. On the contrary, things can happen in apparently random order. In this case, the Godot-message is sent before Vladimir had stated running, and the main script interleaves with the Vladimir script. Of course your code should not rely on any specific order of execution.

In this run some significant things happen from a message passing perspective

  • "Let Godot arrive" (by thread 1)- here a message is sent to (and put in) Vladimir's inbox
  • "Vladimir is waiting" (by thread 11) - Vladimir enters the "receive", pushes the message-handler on the stack, and goes into thread wait
  • "Vladimir saw Godot arrive!" (by thread 11) - the actor runtime (the same thread) realises that there is a message in the inbox; it wakes up and run through the pattern-matching; it finds that the value Godot matches the case Godot and run the corresponding code.

A drawback with this Vladimir design is that each actor needs to have its own thread. The thread will be in wait-state when there is no message processing going on, so no CPU will be wasted. However, the number of threads a system can handle is quite limited so dedicating threads to waiting actors is pretty wasteful. However, that is a letter in its own right.

In contrast to Vladimir who is on his feet the entire play we have the other character, Estragon. As I mentioned, Estragon is more laid back; he sits down and occasionally takes a nap. We will see that he is a pretty good picture of the other actor execution model, the event-based. But that will also need to be the subject of another letter. And, then we want to cover Akka as well ....

Nevertheless, the thread-based actor model that Vladimir represents is a pretty good starting point for understanding actors, message passing, and message-handling execution.

Yours

Dan

ps I have actually made some measurements to see how limited the thread-based execution model actually is.