Агенты Scala: Другое поведение на JRE 1.5 и 1.6

Мое моделирование использует агентов и Scala, С 2.8 снимками. В 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)
  }
}

16
задан Community 23 May 2017 в 12:00
поделиться

2 ответа

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

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

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

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

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

8
ответ дан 30 November 2019 в 23:18
поделиться

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

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

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

Я бы предположил второе?

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

-XX:ThreadPriorityPolicy=1

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

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

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

Удачи!

1
ответ дан 30 November 2019 в 23:18
поделиться
Другие вопросы по тегам:

Похожие вопросы: