My simulation is using actors and Scala 2.8-Snapshot. In Java JRE 1.5 it runs well - all 40 gears (actors) are working simultaneously. Using Java JRE 1.6 only 3 gears are working simultaneously. I tested it with and without GUI: both give same result.
My simulation with GUI is available on github: http://github.com/pmeiclx/scala_gear_simulation
Maybe you remember to my first problem with actors. After solving these problems I did a GUI for the simulation and I got this new "strange" behavior.
Here's the code without GUI:
package ch.clx.actorversions
import actors.Actor
import actors.Actor._
import collection.mutable.ListBuffer
case class ReceivedSpeed(gear: Gear)
case object StartSync
case class SyncGear(controller: GearController, syncSpeed: Int)
object ActorVersion {
def main(args:Array[String]) = {
println("[App] start with creating gears")
val gearList = new ListBuffer[Gear]()
for (i <- 0 until 100) {
gearList += new Gear(i)
}
val gearController = new GearController(gearList)
gearController.start()
gearController ! StartSync
}
}
/**
* CONTROLLER
*/
class GearController(nGears: ListBuffer[Gear]) extends Actor {
private var syncGears = new ListBuffer[Gear]
private var syncSpeed = 0
def act = {
while(true) {
receive {
case StartSync => {
println("[Controller] Send commands for syncing to gears!")
var speeds = new ListBuffer[Int]
nGears.foreach(e => speeds += e.speed)
//Calc avg
//var avgSpeed = speeds.foldLeft(0)(_ + _) / speeds.length
//var avgSpeed = speeds.foldLeft(0) { (x, y) => x + y } / speeds.length
syncSpeed = (0/:speeds)(_ + _) / speeds.length //Average over all gear speeds
//TODO syncSpeed auf Median ausrichten
println("[Controller] calculated syncSpeed: "+syncSpeed)
nGears.foreach{e =>
e.start()
e ! SyncGear(this, syncSpeed)
}
println("[Controller] started all gears")
}
case ReceivedSpeed(gear: Gear) => {
println("[Controller] Syncspeed received by a gear ("+gear.gearId+")")
//println("[Controller] mailboxsize: "+self.mailboxSize)
syncGears += gear
if(syncGears.length == nGears.length) {
println("[Controller] all gears are back in town!")
System.exit(0)
}
}
case _ => println("[Controller] No match :(")
}
}
}
}
/**
* GEAR
*/
class Gear(id: Int) extends Actor {
private var mySpeed = scala.util.Random.nextInt(1000)
private var myController: GearController = null
def speed = mySpeed
def gearId = id
/* Constructor */
println("[Gear ("+id+")] created with speed: "+mySpeed)
def act = {
loop {
react {
case SyncGear(controller: GearController, syncSpeed: Int) => {
//println("[Gear ("+id+")] activated, try to follow controller command (form mySpeed ("+mySpeed+") to syncspeed ("+syncSpeed+")")
myController = controller
adjustSpeedTo(syncSpeed)
}
}
}
}
def adjustSpeedTo(targetSpeed: Int) = {
if(targetSpeed > mySpeed) {
mySpeed += 1
self ! SyncGear(myController, targetSpeed)
}else if(targetSpeed < mySpeed) {
mySpeed -= 1
self ! SyncGear(myController, targetSpeed)
} else if(targetSpeed == mySpeed) {
callController
}
}
def callController = {
println("[Gear ("+id+")] has syncSpeed")
myController ! ReceivedSpeed(this)
}
}
Short answer: change your controller to use loop/react instead of while/receive
The actors library detects which Java version it is running on, and if it is 1.6 (and not IBM's VM) it uses a bundled version of the JSR-166y fork join thread pool, so there is a substantial difference in the underlying implementation depending on Java version.
The fork/join thread pool uses a kind of two-level queue for tasks. Each worker thread has a queue, and there's a shared queue for the pool. Tasks originating in a fork/join thread go directly onto the fork/join thread's queue rather than through the main queue. Task stealing among threads is used to keep threads busy and help avoid starvation.
In your case all of the tasks to start the gears end up on queue for the thread running the controller. Because you're using while/receive in that actor it never lets go of the thread, so it never executes the tasks directly on its queue. The other threads are constantly busy with the 3 gears, so they never attempt to steal work from the thread running the controller. The result is the other gear actors are never executed.
Switching to loop/react in the controller should fix the problem because on every loop the actor lets go of the thread and schedules a new task, which will end up at the back of the queue so the other tasks on it will be executed.
Using Java JRE 1.6 only 3 gears are working simultaneously.
Do you mean that:
I would guess the second?
The difference in observed behavior is probably down to a difference in the JVM implementations - there are changes between JRE 1.5 and JRE 1.6. Some of these changes can be switched off, e.g. by setting a flag like this one:
-XX:ThreadPriorityPolicy=1
... but the second behaviour is a totally valid way to execute your code. It just isn't what you expected because it violates a notion of "fairness" that you have but the work scheduler doesn't. You could add some kind of Clock actor to ensure that the most favoured gear receives no more than (say) 10 "ticks" more than the least favoured actor.
The difference between the JREs is hard to research without knowing:
Good luck!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With