CAF 中的 Actor 引用循环

Actor Reference Cycles in CAF

用户手册的 Breaking Cycles Manually 部分讨论了 actors 相互引用以及如何避免这些场景中的潜在陷阱。我的问题是你如何开始创建一个循环?我经常将一个 spawn 函数创建的句柄传递给另一个 spawn 函数的参数,但我正在努力弄清楚如何为两个演员提供彼此的句柄:

#include<chrono>
#include<iostream>
#include <vector>
#include <string>

#include "caf/typed_event_based_actor.hpp"
#include "caf/scoped_actor.hpp"
#include "caf/caf_main.hpp"

#include "CustomMessages.h"
#include "../DuckParty/Displayable.h"
#include "../DuckParty/Duck.h"
#include "../DuckLibrary/Mallard.h"
#include "../DuckLibrary/TerminalDisplayer.h"

using namespace std::chrono;
using namespace std;

using DisplayActor = caf::typed_actor<
    caf::result<void>(display_behavior, time_point<system_clock>, string)>;

using DuckActor = caf::typed_actor<
    caf::result<void>(do_duck_behavior)>;

class DisplayState {
private:
    unique_ptr<Displayable> displayable_;

public:
    explicit DisplayState(unique_ptr<Displayable> displayable) : displayable_(move(displayable)) {}

    DisplayActor::behavior_type make_behavior() {
        return {
            [this](display_behavior, time_point<system_clock> quack_time, string behavior) {
                displayable_->DisplayBehavior(quack_time, behavior);
            }
        };
    }
};

using DisplayImpl = DisplayActor::stateful_impl<DisplayState>;


class DuckState {
private:
    DuckActor::pointer self_;
    unique_ptr<Duck> duck_;
    int milliseconds_;
    DisplayActor display_actor_;

public:
    explicit DuckState(DuckActor::pointer self, unique_ptr<Duck> duck, int milliseconds, DisplayActor display_actor) :
        self_(self),
        duck_(move(duck)),
        milliseconds_(milliseconds),
        display_actor_(display_actor) {}

    DuckActor::behavior_type make_behavior() {
        self_->send(self_, do_duck_behavior_v);
        return {
        [this](do_duck_behavior) {
            self_->delayed_send(self_, std::chrono::milliseconds(milliseconds_), do_duck_behavior_v);
            time_point<system_clock> quackTime = system_clock::now();
            self_->send(display_actor_, display_behavior_v, quackTime, duck_->GetFlyBehavior() + " " + duck_->GetNoiseBehavior());
            }
        };
    }
};

using DuckImpl = DuckActor::stateful_impl<DuckState>;

void caf_main(caf::actor_system& sys) {
    unique_ptr<Duck> duck = make_unique<Mallard>("Howard the Duck");
    unique_ptr<Displayable> display = make_unique<TerminalDisplayer>();

    DisplayActor display_actor = sys.spawn<DisplayImpl>(move(display));                 // How to give this actor a strong static reference to duck_actor?
    DuckActor duck_actor = sys.spawn<DuckImpl>(move(duck), 500, display_actor);
}

CAF_MAIN(caf::id_block::duck_msg_types)

您可以在我的 main 函数中看到,我可以轻松地为 DuckActor 提供 DisplayActor 的句柄,但如何同时为 DisplayActor 提供 DuckActor 的句柄?关于如何创建参考循环,您有任何示例或建议吗?恐怕我遗漏了一些明显的东西。

Do you have any examples or advice regarding how to create a reference cycle?

一般建议是不要创建它们。 ;)

您已经使用状态 类 构建您的应用程序,这是避免该问题的推荐方法。需要明确的是,两个演员互相控制本身并不是问题,而且一直在发生。消息持有对发件人的引用,这通常会导致两个参与者现在持有彼此的引用。手册警告的循环是永久循环,即使在参与者终止后仍会持续存在。

如果您使用的是有状态 actor,则没有永久循环。状态在终止时被破坏,例如,由于调用 self->quit()。但是,演员 object 本身无法在此时被销毁。 Actor 被引用计数,因此底层 object 一直存在,直到不再引用它为止。如果两个参与者通过成员变量相互引用,就会发生内存泄漏。如果您通过直接从一种参与者类型派生来实现参与者,例如通过从 event_based_actor 继承,您只能 运行 解决这个问题。 CAF 手册和其他资源总是提倡不要从 actor 类型继承,但是 如果你这样做 (你不这样做,因为你遵循了使用状态 类 的最佳实践),你还需要担心潜在的周期。

至于演员如何最终相互推荐:有几种方法。消息指向发件人,因此,如果发件人还持有对收件人的引用,那么通过以某种方式存储发件人信息,您就创建了一个循环。您当然也可以在消息中包含一个参与者句柄,然后以这种方式存储它。在上面的示例中,您在 main 中启动了两个演员。但是,在 server-worker 关系中,您通常让服务器启动工作程序。如果出于某种原因工作人员需要知道他们的 parent,服务器可以将其自身的句柄传递给工作人员。因此,在您的情况下,您可以让显示演员生成鸭子,然后存储鸭子演员句柄。

就 re-iterate 而言,无需考虑应用程序中的循环 除非 您通过直接继承 CAF 角色类型来实现角色 and 您将对其他参与者的引用存储在成员变量中。只要您不这样做,您就可以安全地跳过手册中讨论中断周期的部分。