16

我的模拟是使用演员和 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)
  }
}
4

2 回答 2

8

简短的回答:更改您的控制器以使用循环/反应而不是同时/接收

Actor 库检测它运行在哪个 Java 版本上,如果它是 1.6(而不是 IBM 的 VM),它使用 JSR-166y fork join 线程池的捆绑版本,因此在底层实现方面存在很大差异,具体取决于爪哇版。

fork/join 线程池使用一种两级队列来处理任务。每个工作线程都有一个队列,池中有一个共享队列。源自 fork/join 线程的任务直接进入 fork/join 线程的队列,而不是通过主队列。线程之间的任务窃取用于保持线程忙碌并帮助避免饥饿。

在您的情况下,启动齿轮的所有任务最终都在运行控制器的线程的队列中。因为你在那个actor中使用while/receive,所以它永远不会释放线程,所以它永远不会直接在它的队列上执行任务。其他线程一直忙于这 3 个齿轮,因此它们从不尝试从运行控制器的线程中窃取工作。结果是其他齿轮actor永远不会被执行。

在控制器中切换到循环/反应应该可以解决问题,因为在每个循环中,参与者都会释放线程并安排一个新任务,该任务将最终排在队列的后面,以便执行其上的其他任务。

于 2010-02-20T15:59:18.013 回答
1

使用 Java JRE 1.6 只有 3 个齿轮同时工作。

你的意思是:

  • 只有三个档位可以朝着目标速度前进。当三个档位达到目标速度时,没有更多的档位有任何进展。
  • 任何时候只有三个齿轮取得进展。当三个档位之一达到目标速度时,另一个档位开始前进,直到所有档位都达到目标速度。

我猜第二个?

观察到的行为差异可能归结为 JVM 实现的差异 - JRE 1.5 和 JRE 1.6 之间存在变化。其中一些更改可以关闭,例如通过设置如下标志:

-XX:ThreadPriorityPolicy=1

...但第二种行为是执行代码的完全有效的方式。这不是您所期望的,因为它违反了您所拥有的“公平”概念,但工作调度程序却没有。您可以添加某种时钟参与者,以确保最受青睐的装备比最不受欢迎的参与者多接收(例如)10 个“滴答声”。

JRE 之间的区别很难在不知道的情况下研究:

  • 您正在使用哪个 JRE 更新版本。
  • 您运行哪个操作系统。
  • 你有多少个 CPU 和内核。
  • 代码是否已针对 JRE 1.6 重新编译。

祝你好运!

于 2010-02-20T14:58:15.587 回答