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 ^C10 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 ^C100 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 345524So 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.