Scala Actors: различное поведение на JRE 1.5 и 1.6 - PullRequest
16 голосов
/ 18 февраля 2010

Моя симуляция использует актеров и Scala 2.8-Snapshot. В Java JRE 1.5 он работает хорошо - все 40 передач (актеров) работают одновременно. При использовании Java JRE 1.6 одновременно работают только 3 механизма. Я проверил это с и без GUI: оба дают одинаковый результат.

Моя симуляция с графическим интерфейсом доступна на github: http://github.com/pmeiclx/scala_gear_simulation

Может быть, вы помните мою первую проблему с актерами . После решения этих проблем я сделал графический интерфейс для симуляции и получил новое «странное» поведение.

Вот код без графического интерфейса:

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 голосов
/ 20 февраля 2010

Краткий ответ: измените ваш контроллер, чтобы использовать цикл / реагировать вместо while / receive

Библиотека акторов определяет, на какой версии Java она работает, и, если это версия 1.6 (а не виртуальная машина IBM), она использует пакетную версию пула потоков объединения вилок JSR-166y, поэтому в базовом реализация в зависимости от версии Java.

Пул потоков fork / join использует своего рода двухуровневую очередь для задач. У каждого рабочего потока есть очередь, и есть общая очередь для пула. Задачи, возникающие в потоке fork / join, направляются непосредственно в очередь потока fork / join, а не через основную очередь. Кража задач между потоками используется для обеспечения занятости потоков и предотвращения голодания.

В вашем случае все задачи по запуску передач заканчиваются в очереди для потока, управляющего контроллером. Поскольку вы используете while / receive в этом акторе, он никогда не отпускает поток, поэтому он никогда не выполняет задачи непосредственно в своей очереди. Другие потоки постоянно заняты 3 передачами, поэтому они никогда не пытаются украсть работу из потока, управляющего контроллером. В результате остальные актеры снаряжения никогда не будут казнены.

Переключение на цикл / реакцию в контроллере должно решить проблему, потому что в каждом цикле субъект отпускает поток и планирует новое задание, которое заканчивается в конце очереди, поэтому другие задания в нем будут казнены.

1 голос
/ 20 февраля 2010

При использовании Java JRE 1.6 одновременно работают только 3 передачи.

Вы имеете в виду, что:

  • только три передачи делают прогресс в направлении целевой скорости. Когда три передачи достигают заданной скорости, никакие механизмы больше не прогрессируют.
  • только три передачи прогрессируют одновременно. Когда одна из трех передач достигает целевой скорости, другая начинает прогрессировать, пока все передачи не достигнут целевой скорости.

Я бы догадался, второй?

Разница в наблюдаемом поведении, вероятно, сводится к разнице в реализациях JVM - есть изменения между JRE 1.5 и JRE 1.6. Некоторые из этих изменений могут быть отключены, например, установив флаг, подобный этому:

-XX:ThreadPriorityPolicy=1

... но второе поведение - полностью допустимый способ выполнения вашего кода. Это просто не то, что вы ожидали, потому что это нарушает понятие «справедливости», которое у вас есть, а планировщик работы - нет. Вы можете добавить какого-нибудь актера «Часов», чтобы гарантировать, что наиболее предпочитаемая экипировка получает не более (скажем) на 10 «тиков» больше, чем наименее любимый актер.

Разницу между JRE трудно исследовать, не зная:

  • точно, какие версии обновления JRE вы используете.
  • какую ОС вы используете.
  • сколько у вас процессоров и ядер.
  • был ли код перекомпилирован для JRE 1.6.

Удачи!

...