Scala Actors:JRE 1.5 和 1.6 上的不同行为

发布于 2024-08-21 14:59:27 字数 3632 浏览 1 评论 0原文

我的模拟使用 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

薄情伤 2024-08-28 14:59:27

简短回答:更改控制器以使用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.

别低头,皇冠会掉 2024-08-28 14:59:27

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

您的意思是:

  • 只有三个档位才能达到目标速度。当三个档位达到目标速度时,不再有任何档位取得任何进展。
  • 任何时候只有三个档位取得进展。当三个档位中的一个达到目标速度时,另一个档位开始前进,直到所有档位都达到目标速度。

我猜第二个?

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

-XX:ThreadPriorityPolicy=1

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

就很难研究 JRE 之间的差异

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

祝你好运!

Using Java JRE 1.6 only 3 gears are working simultaneously.

Do you mean that:

  • only three gears make any progress towards the target speed. When the three gears reach the target speed no more gears make any progress.
  • only three gears make progress at any one time. When one of the three gears reaches the target speed, another gear starts making progress until all gears have reached the target speed.

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:

  • exactly which JRE update versions you are using.
  • which OS you run.
  • how many CPUs and cores you have.
  • whether the code has been recompiled for JRE 1.6.

Good luck!

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文