为啥在actor中创建actor是危险的

Posted

技术标签:

【中文标题】为啥在actor中创建actor是危险的【英文标题】:Why creating an actor within actor is dangerous为什么在actor中创建actor是危险的 【发布时间】:2017-11-16 03:07:19 【问题描述】:

akka documentation 明确表示在这样的actor中创建actor是危险的:

class ActorA extends Actor 
  def receive = ???


final class ActorB extends Actor 
  def receive = 
  case _ =>
  val act = context.actorOf(Props(new ActorA))

我了解 Actor 的 apply 方法正在接受创建 Actor 的 this 引用。但我无法理解(也找不到任何示例)为什么这是有害的以及它会导致什么问题?

【问题讨论】:

看来这篇好文章可以帮到你:cakesolutions.net/teamblogs/… @Leonard,谢谢我看到那篇很棒的文章,但是,IMO,它没有解释“为什么它有害”。请注意,其中一位读者“Landlocked”提出了同样的问题 我发现了这个:groups.google.com/forum/#!topic/akka-user/AnWHbESgCHQ 我认为 sender() 示例给出了很好的解释 @thwiegan 发件人示例是一个不好的解释,因为此代码就像您将发件人包装到 Future 中一样。这个例子没有解释一个主要的问题,即在Props(new)break the actor encapsulation的actor中创建一个actor。 @Leonard 我认为它在某种程度上与您发布的文章相结合。 sender() 示例显示,如果您将状态泄漏到参与者之外会发生什么。您发布的文章显示,使用 Props(new) 会泄漏状态(由于 scala 编译器行为,通过泄漏 this 引用)并因此破坏了参与者封装。 【参考方案1】:

让我们稍微调整一下您的示例

class ActorA(str:String) extends Actor 
  def receive = ???


final class ActorB extends Actor 
  def receive = 
  case _ =>
  val act = context.actorOf(Props(new ActorA("hidden")))

使用 Actor 的大多数常见用例是处理故障转移和监督,当一个 Actor 失败并需要重新启动时,Actor 系统需要知道如何做到这一点。使用 Props(Props(new ActorA)) 时,你已经通过自己处理隐藏了“hidden”的参数值。

如果您声明如何创建演员的实例,而不是这样做,那么演员系统将确切地知道在重新创建演员时它需要做什么 - 即创建一个带有“隐藏”构造函数参数的 ActorA 实例。

即使是没有参数的 Actor 示例

context.actorOf(Props(new ActorA))

不推荐在另一个actor中实例化actor的这种方式,因为它鼓励关闭封闭范围,导致不可序列化的Props和可能的竞争条件(破坏actor封装)。

【讨论】:

我不确定我得到你了,你能举个例子说明它会有害吗?【参考方案2】:

我相信我们混淆了创建和声明。医生说

Declaring one actor within another is very dangerous and breaks actor encapsulation. Never pass an actor’s this reference into Props!

所以问题是声明,而不是创建! 让我们看看Java的:

public class MyActor extends AbstractActor 
    @Override
    public Receive createReceive() 
        return ReceiveBuilder.create()
                .match(String.class, handleString())
                .matchAny(x -> unhandled(x))
                .build();
    

    private FI.UnitApply<String> handleString() 
        return message -> sender().tell("OK", getSelf());
    

    class MyOtherActor extends AbstractActor 

        @Override
        public Receive createReceive() 
            return ReceiveBuilder.create()
                    .match(String.class, handleString())
                    .matchAny(x -> unhandled(x))
                    .build();
        

        private FI.UnitApply<String> handleString() 
            return message -> sender().tell("OK-Inner", getSelf());
        
    

现在,如果 MyOtherActor 是一个普通类,我们只能从 MyActor 的实例中实例化它:

MyActor actor = new MyActor();
MyActor.MyOtherActor otherActor = actor.new MyOtherActor();

这意味着 MyOtherActor 的构造函数依赖于 MyActor 的实例!

现在,如果 Props 应该包含演员的“工厂”。他们需要一个工厂方法。如果我们的 MyOtherActor 像我们在这里所做的那样声明,那么我们的 props 将如下所示(ish):

MyActor actor = ??? // how did you even get a reference to the actor and not the actorRef in the first place!
Props otherActorProps = Props.create(MyActor.MyOtherActor.class, () -> actor.new MyOtherActor());

然后砰,踢球者来了!现在您的otherActorProps 包含对actor 的引用,即您已经关闭了可变状态!如果出于某种原因actor“死亡”,您的道具仍将引用它,从而导致各种奇怪。

还有一个问题是如何首先获得对actor 的引用,而不是actorRef

恕我直言,这就是文档所指的,而不是在另一个演员中“创建”(即实例化、生成)演员的事实:这绝对是正常的,这是 akka 的例行操作(这就是为什么你可以这样做@ 987654330@和actorSystem.actorOf(...)

【讨论】:

【参考方案3】:

警告在文档中,因为很容易意外关闭创建actor的状态,包括它的this指针(你不应该在基于actor的代码中使用它)。

根据我的经验,我经常看到将props 方法放入演员的伴生对象中:

object ActorA 
  def props() = Props(new ActorA)

这样做可以确保返回的Props 不会关闭参与者的状态。

class ActorB extends Actor 
  def receive = 
    case _ =>
      val actorB = context.actorOf(ActorA.props)
       ...
  

对于不接受构造函数参数的actor来说可能性不大,但是一旦参数发挥作用,你需要小心关闭内部状态。

【讨论】:

以上是关于为啥在actor中创建actor是危险的的主要内容,如果未能解决你的问题,请参考以下文章

Akka actor、Futures 和闭包

Java Akka Actor 和流

(中等)SQL练习36:创建一个actor_name表

为啥在我的 JavaScript 浏览器游戏中弹跳 Actor 不起作用?

可能有危险的文本输入处理

为啥硝酸和硝酸盐类是特定种类危险化学品