2010-02-18 17 views
16

Mi simulación utiliza actores y Scala 2.8-Snapshot. En Java JRE 1.5 funciona bien: los 40 engranajes (actores) funcionan simultáneamente. Con Java JRE 1.6, solo 3 engranajes funcionan simultáneamente. Lo probé con y sin GUI: ambos dan el mismo resultado.Scala Actores: comportamiento diferente en JRE 1.5 y 1.6

Mi simulación con interfaz gráfica de usuario está disponible en GitHub: http://github.com/pmeiclx/scala_gear_simulation

Tal vez usted recuerde a my first problem with actors. Después de resolver estos problemas hice una GUI para la simulación y obtuve este nuevo comportamiento "extraño".

Aquí está el código sin interfaz gráfica de usuario:

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) 
    } 
} 

Respuesta

8

Respuesta corta: cambiar su controlador de utilizar bucle/reaccionar en lugar de tiempo/recibir

Los actores detecta la biblioteca de la versión de Java que se ejecuta en, y si es 1.6 (y no de IBM VM) que utiliza una versión empaquetada del conjunto de subprocesos de unión de horquilla JSR-166y, por lo que hay una diferencia sustancial en la implementación subyacente según la versión de Java.

El grupo de subprocesos fork/join utiliza un tipo de cola de dos niveles para tareas.Cada subproceso de trabajo tiene una cola y hay una cola compartida para el grupo. Las tareas que se originan en un hilo de horquilla/unión van directamente a la cola de la horquilla/unión en lugar de a la cola principal. El robo de tareas entre hilos se utiliza para mantener los hilos ocupados y ayudar a evitar la inanición.

En su caso, todas las tareas para iniciar los engranajes terminan en cola para el subproceso que ejecuta el controlador. Debido a que está usando while/receive en ese actor, nunca suelta el hilo, por lo que nunca ejecuta las tareas directamente en su cola. Los otros hilos están constantemente ocupados con los 3 engranajes, por lo que nunca intentan robar el trabajo del hilo que ejecuta el controlador. El resultado es que los otros actores del engranaje nunca se ejecutan.

Cambiar al ciclo/reaccionar en el controlador debería solucionar el problema porque en cada bucle el actor suelta el hilo y programa una nueva tarea, que terminará en la parte posterior de la cola para que las otras tareas ser ejecutado.

+0

FYI: Le expliqué este problema a Philipp Haller y lo arregló en el maletero. Entonces, cuando se libere 2.8, no debería tener el problema. https://lampsvn.epfl.ch/trac/scala/changeset/20950/scala/trunk/src/actors –

+0

Disculpe, estaba un poco ocupado. Con la nueva instantánea, funciona. No es perfecto, pero funciona. ¡Gracias! – meip

1

Uso de Java JRE 1.6 a 3 engranajes están trabajando simultáneamente.

¿Quiere decir que:

  • sólo tres engranajes de hacer ningún progreso hacia la velocidad objetivo. Cuando los tres engranajes alcanzan la velocidad objetivo, no hay más engranajes que hagan ningún progreso.
  • solo tres marchas progresan en cualquier momento. Cuando uno de los tres engranajes alcanza la velocidad objetivo, otro engranaje comienza a progresar hasta que todos los engranajes hayan alcanzado la velocidad objetivo.

Supongo que el segundo?

La diferencia en el comportamiento observado probablemente se deba a una diferencia en las implementaciones de JVM; hay cambios entre JRE 1.5 y JRE 1.6. Algunos de estos cambios se pueden desactivar, p. estableciendo una bandera como esta:

-XX:ThreadPriorityPolicy=1 

... pero el segundo comportamiento es una forma totalmente válida de ejecutar su código. Simplemente no es lo que esperabas porque viola la noción de "equidad" que tienes, pero el programador del trabajo no. Podría agregar algún tipo de actor de Reloj para asegurarse de que el engranaje más favorecido no reciba más que (por decir) 10 "tics" más que el actor menos favorecido.

La diferencia entre los JRE es difícil de investigación sin saber:

  • exactamente qué versiones de actualización del JRE que está utilizando.
  • que sistema operativo ejecuta.
  • cuántas CPU y núcleos tiene.
  • si el código ha sido recompilado para JRE 1.6.

Buena suerte!

Cuestiones relacionadas