Scala Actors:JRE 1.5 和 1.6 上的不同行为
我的模拟使用 actor 和 Scala 2.8-Snapshot。在 Java JRE 1.5 中它运行良好 - 所有 40 个齿轮(参与者)都同时工作。使用 Java JRE 1.6 只有 3 个齿轮同时工作。我在使用和不使用 GUI 的情况下对其进行了测试:两者都给出相同的结果。
我的 GUI 模拟可以在 github 上找到: http://github.com/pmeiclx/scala_gear_simulation
也许你记得我与演员的第一个问题。解决这些问题后,我做了一个 GUI 进行模拟,并得到了这个新的“奇怪”行为。
这是没有 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)
}
}
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)
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
简短回答:更改控制器以使用loop/react而不是while/receive
Actor库检测它正在运行的Java版本,如果它是1.6(而不是IBM的VM),它使用JSR-166y fork的捆绑版本加入线程池,因此根据Java版本,底层实现存在很大差异。
fork/join 线程池使用一种两级队列来执行任务。每个工作线程都有一个队列,并且池中有一个共享队列。源自 fork/join 线程的任务直接进入 fork/join 线程的队列,而不是通过主队列。线程之间的任务窃取用于保持线程繁忙并帮助避免饥饿。
在您的情况下,启动齿轮的所有任务最终都会出现在运行控制器的线程的队列中。因为您在该参与者中使用 while/receive,所以它永远不会释放线程,因此它永远不会直接在其队列上执行任务。其他线程一直忙于 3 个齿轮,因此它们从不尝试从运行控制器的线程窃取工作。结果是其他齿轮演员从未被处决。
在控制器中切换到循环/反应应该可以解决这个问题,因为在每个循环中,参与者都会释放线程并调度一个新任务,该任务将最终位于队列的后面,因此队列中的其他任务将被执行。
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.
您的意思是:
我猜第二个?
观察到的行为差异可能归因于 JVM 实现的差异 - JRE 1.5 和 JRE 1.6 之间存在变化。其中一些更改可以关闭,例如通过设置如下所示的标志:
...但第二种行为是执行代码的完全有效的方式。这不是您所期望的,因为它违反了您所拥有的“公平”概念,但工作调度程序却没有。您可以添加某种时钟 actor,以确保最受青睐的装备比最不受青睐的 actor 接收的时间不超过(比如说)10 个“刻度”。
就很难研究 JRE 之间的差异
祝你好运!
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:
... 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!