After reading about using react
in actors in Scala, I thought react
's would share the same thread given there weren't multiple react
's pending. It doesn't seem to be the case.
import scala.actors.Actor
import scala.actors.Actor._
class SleepyReactor extends Actor {
def act() {
loop {
react {
case x => {
println("reacting to %s on thread %s".format(x, Thread.currentThread.getName))
Thread.sleep(1000)
println("done with " + x)
}
}
}
}
}
val sleepyOne = new SleepyReactor
sleepyOne.start
sleepyOne ! "first" // runs on thread-5
// wait until completion
sleepyOne ! "second" // runs on thread-3
Can someone explain why these react
's are running on different threads and when a new thread is created for an actor with react
?
I read somewhere react
is event based, and I took that to mean that "react actors" shared a thread and if one was "reacting" the other "react actors" would be queued until the first was done. I now think I am wrong. How does this work, and how is it different than receive?
It is true that for a pure event-based actor, its reacting code runs on the same thread as message sending code.
But in Scala, since it's not desirable to block a thread when an actor calls a blocking operation inside its react code and to unify event-based and thread-based actors(being able to compose them), both type of actors uses the same thread pool but the thread-based actors get their own threads whereas event-based actors shares threads based on a task queue. For details, please see Actors that Unify Threads and Events by Philipp Haller and Martin Odersky
Don't assume a separate thread per Actor. The Scala machinery creates a pool of worker threads and only grows that pool if the size of 'blocked' Actors is greater than the pool size. When your actor calls receive
, it's in a blocked state until it receives its message.
To see the effect described in the previous answers, you need to generate more than two threads. This sample program generates 100 threads with receive and 100 threads with react.
When you run it, you can see that the receive actors each occupy a thread, and the react ones share a small number of threads. (It's easiest to see when you sort the output.)
import scala.actors._
import scala.actors.Actor._
class ReactActor extends Actor {
def act {
loop {
react {
case 'Hello => println("React: " + Thread.currentThread)
}
}
}
}
class ReceiveActor extends Actor {
def act {
while (true) {
receive {
case 'Hello => println("Receive: " + Thread.currentThread)
}
}
}
}
object Main {
def main(args: Array[String]) {
val count = 100
val as = new Array[Actor](2 * count)
for (i <- 0 until count) {
as(i) = new ReactActor
as(count + i) = new ReceiveActor
}
for (a <- as) a.start()
actor {
println(Thread.currentThread)
for (a <- as) a ! 'Hello
}
}
}
The sorted output in a typical program run:
Thread[Thread-102,5,main]
React: Thread[Thread-102,5,main]
React: Thread[Thread-102,5,main]
React: Thread[Thread-102,5,main]
...
React: Thread[Thread-102,5,main]
React: Thread[Thread-103,5,main]
React: Thread[Thread-103,5,main]
...
React: Thread[Thread-103,5,main]
React: Thread[Thread-104,5,main]
React: Thread[Thread-104,5,main]
React: Thread[Thread-104,5,main]
...
React: Thread[Thread-104,5,main]
React: Thread[Thread-105,5,main]
React: Thread[Thread-105,5,main]
React: Thread[Thread-105,5,main]
...
React: Thread[Thread-105,5,main]
Receive: Thread[Thread-1,5,main]
Receive: Thread[Thread-10,5,main]
Receive: Thread[Thread-100,5,main]
Receive: Thread[Thread-101,5,main]
Receive: Thread[Thread-11,5,main]
Receive: Thread[Thread-12,5,main]
Receive: Thread[Thread-13,5,main]
Receive: Thread[Thread-14,5,main]
Receive: Thread[Thread-15,5,main]
Receive: Thread[Thread-16,5,main]
Receive: Thread[Thread-17,5,main]
Receive: Thread[Thread-18,5,main]
Receive: Thread[Thread-19,5,main]
Receive: Thread[Thread-2,5,main]
Receive: Thread[Thread-20,5,main]
Receive: Thread[Thread-21,5,main]
...
The scheduler library uses a thread pool to control execution of the actors. I don't know the specifics of the logic it uses, but, to me, it would seem natural to expect it to:
Initialize with more than one thread in the pool, as multithreaded applications are very likely to use more than one thead.
Select the thread to be used with a waiting actor in a queue-like manner -- threads are freed to the end of the queue, and acquire from the beginning of the queue.
Also, I assume some threads are used to handle the scheduling itself as well as message passing.