Подтвердить что ты не робот

Scala Актеры: различное поведение на JRE 1.5 и 1.6

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

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

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

Здесь код без 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)
  }
}
4b9b3361

Ответ 1

Короткий ответ: измените свой контроллер на использование цикла/реакции вместо while/receive

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

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

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

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

Ответ 2

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

Вы имеете в виду это:

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

Я бы предпочел второй?

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

-XX:ThreadPriorityPolicy=1

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

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

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

Удачи!