Speisende Philosophen


Nach fast einem Jahr hat sich jemand meiner verhungernden Philosophen angenommen und den Fehler gefunden: Ein hungriger Philosoph, der nur eine Gabel hat, gibt diese auf Anfrage seinem Nachbarn, fordert sie aber nie zurück. Ein großes Dankeschön an Dominik, der diesen Bug entdeckt hat!

object philosophers {

  import scala.actors.Actor
  import Actor._
  import scala.util.Random
  import java.util.{Timer, TimerTask}

  val timer = new Timer

  case object Waiting
  case object ReadyEating
  case class ForkRequest(philosopher:Philosopher)
  case class ForkFrom(philosopher:Philosopher)

  sealed trait Fork
  case object NoFork extends Fork
  case object DirtyFork extends Fork
  case object CleanFork extends Fork
  case object OccupiedFork extends Fork

  class Side(val phil:Philosopher, name:String) {
    var fork = if (name < phil.name) DirtyFork else NoFork
    var request = false
  }

  class Philosopher(val name:String) extends Actor {

    lazy val sides = Array(
      new Side(getLeftNeighbor(this), name),
      new Side(getRightNeighbor(this), name))

    var hungry = false

    def tryToEat: Unit = if(sides.forall(_.fork != NoFork)) {
        sides.foreach(_.fork = OccupiedFork)
        hungry = false
        println(name + " is eating")
        schedule(1000 + Random.nextInt(5000), this, ReadyEating)
      }

    def askForForks: Unit = sides.filter(_.fork == NoFork).foreach {s =>
        s.phil ! ForkRequest(this)
        printf("%s asks %s for a fork%n", name, s.phil.name)
      }

    def readyEating: Unit = sides.foreach(_.fork = DirtyFork)

    def handlePendingRequests: Unit = sides.filter(s => s.request && s.fork == DirtyFork)
      .foreach{ s =>
        s.fork = NoFork
        s.request = false
        s.phil ! ForkFrom(this)
        if (hungry) {
          s.phil ! ForkRequest(this)
          printf("%s asks %s for a fork%n", name, s.phil.name)
        }
      }

    def receiveFork(philosopher:Philosopher): Unit =
        sides.find(s => s.phil == philosopher && s.fork == NoFork)
             .getOrElse(error("Don't want this fork")).fork = CleanFork

    def forkCount: Int = sides.count(_.fork != NoFork)

    def act(){
      loop {
        receive {
          case ForkRequest(philosopher) =>
            sides.find(s => s.phil == philosopher)
                 .getOrElse(error("Dunno that guy")).request = true
            handlePendingRequests

          case ForkFrom(philosopher) =>
            receiveFork(philosopher)
            tryToEat

          case Waiting =>
            println(name + " is hungry")
            hungry = true
            askForForks
            tryToEat

          case ReadyEating =>
            println(name + " ate")
            readyEating
            handlePendingRequests
            think

          case msg => error("unknown message " + msg)
        }
      }
    }

    def think {
      println(name + " thinks")
      schedule(1000 + Random.nextInt(5000), this, Waiting)
    }

    def schedule(ms:Long, actor:Actor, msg:Any) {
      timer.schedule(new TimerTask{ def run() { actor ! msg } }, ms)
    }
  }

  val philosophers = 
    Array("Nietzsche", "Kant", "Feuerbach","Hegel", "Fichte")
          .map(new Philosopher(_))

  def getLeftNeighbor(philosopher:Philosopher) =
    philosophers((philosophers.indexOf(philosopher) + 4) % 5)
  def getRightNeighbor(philosopher:Philosopher) =
    philosophers((philosophers.indexOf(philosopher) + 1) % 5)

  def main(args: Array[String]) {
    philosophers.foreach(_.start)
    philosophers.foreach(_.think)

    //just a check that we don't lose any forks
    timer.scheduleAtFixedRate(new TimerTask{ def run() {
          println(philosophers.map(_.forkCount).sum + " forks.") }
      }, 0, 5000)
  }
}

Ich habe die vorgeschlagene Lösung mit der „hungry“-Variablen eingebaut und den Code etwas gestrafft. Dazu habe ich eine Klasse „Side“ eingeführt, die jeweils für die linke oder rechte Seite den Nachbar-Philosophen, die Gabel und den Anfrage-Status („will dieser Nachbar meine Gabel?“) zusammenfasst. Dadurch konnte ich die ganzen links- und rechtsseitigen Abfragen durch Collection-Operationen ersetzen. Ich hoffe, der Code ist dadurch etwas lesbarer geworden.

Übrigens habe ich auch die Scala-Links-Seite wieder mal aktualisiert.

Advertisements

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s