Scala演员:在JRE 1.5和1.6上表现不同。

16

我的模拟使用Scala 2.8-Snapshot和actor。在Java JRE 1.5中,它运行良好 - 所有40个齿轮(actors)同时工作。在Java JRE 1.6中,只有3个齿轮同时工作。我测试过带GUI和不带GUI的情况:两者结果相同。

我的带GUI的模拟可在github上找到:http://github.com/pmeiclx/scala_gear_simulation

也许你还记得我之前遇到的与actors相关的问题。在解决了这些问题后,我为模拟编写了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)
  }
}
2个回答

8
简短回答:将您的控制器改成使用循环/反应,而不是while/接收。
actors库会检测它正在运行的Java版本,如果是1.6(而不是IBM的VM),它将使用JSR-166y fork join线程池的捆绑版本,因此根据Java版本,底层实现存在实质差异。
fork/join线程池使用一种双级队列来处理任务。每个工作线程都有一个队列,并且池中有一个共享队列。起源于fork/join线程的任务直接进入该线程的队列,而不是通过主队列。线程之间进行任务窃取以保持线程忙碌并避免饥饿。
在您的情况下,启动齿轮的所有任务最终都进入控制器运行的线程的队列。由于您在该actor中使用while/receive,因此它永远不会释放线程,因此永远不会直接执行其队列上的任务。其他线程不断忙于处理3个齿轮,因此从运行控制器的线程中偷取工作的尝试也从未发生。结果是其他齿轮演员永远不会被执行。
在控制器中切换到循环/反应应该可以解决问题,因为在每个循环中,actor都会释放线程并调度一个新任务,该任务将进入队列的末尾,因此将执行其他任务。

FYI:我向Philipp Haller解释了这个问题,他已经在主干中修复了它。因此,当2.8版本发布时,它不应该有这个问题。https://lampsvn.epfl.ch/trac/scala/changeset/20950/scala/trunk/src/actors - Erik Engbrecht
抱歉,我有点忙。使用新的快照可以工作了。虽然不完美,但是能用。谢谢! - meip

1
只使用Java JRE 1.6时,只有3个齿轮同时工作。
您的意思是:
- 只有三个齿轮朝着目标速度前进。当这三个齿轮达到目标速度时,没有更多的齿轮会继续前进。 - 只有三个齿轮在任何时候都在前进。当其中一个齿轮达到目标速度时,另一个齿轮开始前进,直到所有齿轮都达到目标速度。
我猜是第二种情况?
观察到的行为差异可能是由于JVM实现的差异 - 在JRE 1.5和JRE 1.6之间存在变化。其中一些变化可以关闭,例如通过设置像这样的标志:
-XX:ThreadPriorityPolicy=1

...但第二种行为是完全有效的执行代码的方式。它只是不符合您的公平性概念,但工作调度程序并没有这样的概念。您可以添加某种时钟演员来确保最受欢迎的齿轮比最不受欢迎的演员多不超过(比如)10个“滴答声”。

在不知道以下信息的情况下,很难研究JRE之间的差异:

  • 您使用的确切JRE更新版本。
  • 您运行的操作系统。
  • 您有多少个CPU和核心。
  • 代码是否已重新编译为JRE 1.6。

祝你好运!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接