Мое моделирование использует агентов и 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)
}
}
Короткий ответ: измените ваш контроллер, чтобы использовать цикл/реакцию вместо 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 в контроллере должен решить эту проблему, потому что при каждом цикле актор отпускает поток и планирует новую задачу, которая окажется в конце очереди, так что другие задачи на ней будут выполнены.
При использовании Java JRE 1.6 одновременно работают только 3 шестеренки.
Вы имеете в виду, что:
Я бы предположил второе?
Разница в наблюдаемом поведении, вероятно, связана с разницей в реализации JVM - есть изменения между JRE 1.5 и JRE 1.6. Некоторые из этих изменений можно отключить, например, установив флаг, подобный этому:
-XX:ThreadPriorityPolicy=1
... но второе поведение - это совершенно правильный способ выполнения вашего кода. Это просто не то, что вы ожидали, потому что это нарушает понятие "справедливости", которое есть у вас, но нет у планировщика работы. Вы можете добавить некий агент Clock, чтобы гарантировать, что наиболее предпочтительная передача получает не более чем (скажем) на 10 "тиков" больше, чем наименее предпочтительный агент.
Разницу между JRE трудно исследовать, не зная:
Удачи!