2010-02-18 12 views
16

Ma simulation utilise des acteurs et Scala 2.8-Snapshot. En Java JRE 1.5 ça marche bien - les 40 gears (acteurs) travaillent simultanément. En utilisant Java JRE 1.6 seulement 3 vitesses travaillent simultanément. Je l'ai testé avec et sans interface graphique: les deux donnent le même résultat.Scala Acteurs: comportement différent sur JRE 1.5 et 1.6

Ma simulation avec une interface graphique est disponible sur github: http://github.com/pmeiclx/scala_gear_simulation

Peut-être que vous vous souvenez de my first problem with actors. Après avoir résolu ces problèmes, j'ai fait une interface graphique pour la simulation et j'ai obtenu ce nouveau comportement "étrange".

Voici le code sans interface graphique:

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

Répondre

8

Réponse courte: changer votre contrôleur pour utiliser la boucle/réagir au lieu de tout/recevoir

Les acteurs Détecte bibliothèque version Java, il est en cours d'exécution, et si elle est de 1,6 (et non VM IBM), il utilise un version groupée du pool de threads JSR-166y fork, il existe donc une différence substantielle dans l'implémentation sous-jacente en fonction de la version Java.

Le pool de threads fork/join utilise une sorte de file d'attente à deux niveaux pour les tâches.Chaque thread de travail a une file d'attente, et il y a une file d'attente partagée pour le pool. Les tâches provenant d'un thread fork/join vont directement dans la file d'attente fork/join thread plutôt que dans la file d'attente principale. Le vol de tâche parmi les threads est utilisé pour garder les threads occupés et éviter la famine.

Dans votre cas, toutes les tâches de démarrage des engrenages se terminent dans la file d'attente du thread exécutant le contrôleur. Parce que vous utilisez while/receive dans cet acteur, il ne lâche jamais le thread, donc il n'exécute jamais les tâches directement dans sa file d'attente. Les autres threads sont constamment occupés avec les 3 vitesses, donc ils ne tentent jamais de voler du travail sur le thread exécutant le contrôleur. Le résultat est que les autres acteurs ne sont jamais exécutés. Passer en boucle/réagir dans le contrôleur devrait résoudre le problème car à chaque boucle l'acteur lâche le thread et planifie une nouvelle tâche, qui se retrouvera au fond de la file d'attente pour que les autres tâches être exécuté.

+0

FYI: J'ai expliqué ce problème à Philipp Haller et il l'a corrigé dans le coffre. Donc, lorsque 2.8 est libéré, il ne devrait pas avoir le problème. https://lampsvn.epfl.ch/trac/scala/changeset/20950/scala/trunk/src/actors –

+0

Désolé, j'étais un peu occupé. Avec le nouvel instantané, cela fonctionne. Pas parfait mais ça marche. Je vous remercie! – meip

1

Utilisation de Java JRE 1.6 seulement 3 vitesses fonctionnent simultanément.

Voulez-vous dire que:

  • seulement trois vitesses font des progrès vers la vitesse cible. Lorsque les trois vitesses atteignent la vitesse cible, plus aucune vitesse ne progresse.
  • seulement trois vitesses font des progrès à tout moment. Lorsque l'un des trois rapports atteint la vitesse cible, un autre rapport commence à progresser jusqu'à ce que tous les rapports aient atteint la vitesse cible.

Je suppose que la seconde?

La différence dans le comportement observé est probablement due à une différence dans les implémentations JVM - il existe des changements entre JRE 1.5 et JRE 1.6. Certains de ces changements peuvent être désactivés, par ex. en définissant un drapeau comme celui-ci:

-XX:ThreadPriorityPolicy=1 

... mais le second comportement est une manière totalement valide d'exécuter votre code. Ce n'est pas ce que vous attendiez parce que cela viole une notion d'équité que vous avez, mais pas le planificateur de travail. Vous pouvez ajouter une sorte d'acteur Clock pour vous assurer que l'équipement le plus favorisé ne reçoit pas plus que (disons) 10 "ticks" de plus que l'acteur le moins favorisé.

La différence entre les JREs est difficile à la recherche sans savoir:

  • exactement quelles versions de mise à jour JRE que vous utilisez.
  • quel OS vous exécutez.
  • combien de processeurs et de cœurs vous avez.
  • si le code a été recompilé pour JRE 1.6.

Bonne chance!