Akka:将不匹配的邮件保留在邮箱中

时间:2018-12-19 08:22:27

标签: scala akka akka-actor

我熟悉Erlang / Elixir,其中进程邮箱中的消息会保留在邮箱中直到匹配:

  

模式Pattern在邮箱中按时间顺序与第一个消息顺序匹配,然后与第二个匹配,依此类推。如果匹配成功并且可选的保护序列GuardSeq为true,则将评估对应的Body。匹配的邮件将被使用,即从邮箱中删除,而邮箱中的所有其他邮件保持不变。

http://erlang.org/doc/reference_manual/expressions.html#receive

但是,对于Akka Actor,不匹配的消息将从邮箱中删除。 在餐饮哲学家模拟中实现例如分叉时,这很烦人:

import akka.actor._

object Fork {
  def props(id: Int): Props = Props(new Fork(id))
  final case class Take(philosopher: Int)
  final case class Release(philosopher: Int)
  final case class TookFork(fork: Int)
  final case class ReleasedFork(fork: Int)
}

class Fork(val id: Int) extends Actor {
  import Fork._

  object Status extends Enumeration {
    val FREE, TAKEN = Value
  }

  private var _status: Status.Value = Status.FREE
  private var _held_by: Int = -1

  def receive = {
    case Take(philosopher) if _status == Status.FREE => {
      println(s"\tPhilosopher $philosopher takes fork $id.")
      take(philosopher)
      sender() ! TookFork(id)
      context.become(taken, false)
    }
    case Release(philosopher) if _status == Status.TAKEN && _held_by == philosopher => {
      println(s"\tPhilosopher $philosopher puts down fork $id.")
      release()
      sender() ! ReleasedFork(id)
      context.unbecome()
    }
  }

  def take(philosopher: Int) = {
    _status  = Status.TAKEN
    _held_by = philosopher
  }

  def release() = {
    _status  = Status.FREE
    _held_by = -1
  }
}

在向分叉发送Take(<philosopher>)消息时, 我们希望邮件保留在邮箱中,直到释放分支并匹配邮件为止。但是,在Akka Take(<philosopher>)中,如果当前正在使用分叉,则会从邮箱中删除邮件,因为没有匹配项。

当前,我通过重写Fork actor的unhandled方法并将消息再次转发到fork来解决此问题:

override def unhandled(message: Any): Unit = {
  self forward message
}

我认为这是非常低效的,因为它一直将消息发送到派生直到匹配。是否有另一种方法可以解决此问题,而无需连续转发不匹配的邮件?

我认为,在最坏的情况下,我将必须实现模仿Erlang邮箱的自定义邮箱类型,如此处所述:http://ndpar.blogspot.com/2010/11/erlang-explained-selective-receive.html


编辑:我根据蒂姆的建议修改了我的实现,并按照建议使用了Stash特性。我的Fork演员现在看起来如下:

class Fork(val id: Int) extends Actor with Stash {
  import Fork._

  // Fork is in "taken" state
  def taken(philosopher: Int): Receive = {
    case Release(`philosopher`) => {
      println(s"\tPhilosopher $philosopher puts down fork $id.")
      sender() ! ReleasedFork(id)
      unstashAll()
      context.unbecome()
    }
    case Take(_) => stash()
  }

  // Fork is in "free" state
  def receive = {
    case Take(philosopher) => {
      println(s"\tPhilosopher $philosopher takes fork $id.")
      sender() ! TookFork(id)
      context.become(taken(philosopher), false)
    }
  }
}

但是,我不想到处都写stash()unstashAll()调用。相反,我想实现一个自定义邮箱类型来为我完成此操作,即存储未处理的消息,并在参与者处理消息后将其取消隐藏。这可能吗?

我尝试实现一个自定义邮箱来执行此操作,但是,我无法确定邮件是否匹配接收块。

2 个答案:

答案 0 :(得分:1)

forward的问题在于,如果有多个等待处理的消息,可能会重新排序消息,这可能不是一个好主意。

这里最好的解决方案似乎是在Actor内部实现自己的队列,该队列提供所需的语义。如果您不能立即处理消息,则将其放入队列中,当下一条消息到达时,您可以处理尽可能多的队列。这也可以让您检测发件人何时发出不一致的消息(例如在Release上的分叉上发出Take),否则这些消息只会在传入的邮箱中累积。

在您可以证明这是一个问题之前,我不会担心效率,但是如果每个接收功能仅处理该特定状态下相关的消息,效率将会更高。


通过将状态放入参数var中,可以避免在actor中使用receive_status值隐含在接收处理程序的选择中,不需要存储为值。 taken接收处理程序仅需要处理Release条消息,而主接收处理程序仅需要处理Take条消息。

答案 1 :(得分:0)

Akka存储库中存在a sample project,其中包含"Dining Philosophers"问题的多个实现。您的方法与他们的方法之间的主要区别在于,它们既将器物和哲学家都实现为演员,而您仅将器物定义为参与者。示例实现显示了如何在不处理未处理消息或使用自定义邮箱的情况下对问题进行建模。