如何在 Erlang/OTP 中将主管的孩子 pid 共享给另一个孩子

Posted

技术标签:

【中文标题】如何在 Erlang/OTP 中将主管的孩子 pid 共享给另一个孩子【英文标题】:How to share supervisor's child pid to another child in Erlang/OTP 【发布时间】:2012-11-11 07:48:36 【问题描述】:

在我的 Erlang/OTP 应用程序中,我有一个 one_for_all 主管 (sup) 和几个孩子。其中一个孩子(child1 具有gen_server 行为)应该能够向另一个孩子(child2 具有supervisor 行为)发送消息。当然,我可以注册它,但是用多余的名称堵塞全局范围似乎不是一个好主意。

因此,使这种交互成为可能的唯一方法是为 child1 提供 child2 的 pid。说到做到。有具有适当功能的supervisor:wich_children/1 呼叫。只需将 sup 的 pid 作为参数传递给 chidl1,在 child1:init 中调用 which_children,然后……就会陷入僵局。 sup 正在等待 child1 开始,child1 正在等待 sup 以获取儿童描述:

init(SupPid) ->
    Descriptions = supervisor:which_children(SupPid),
    ... .

这可以通过以下方法解决:

init(SupPid) ->
    gen_server:cast(self(), initialize),
    ... .

handle_cast(initialize, State) ->
    Descriptions = supervisor:which_children(SupPid),
    ...  % Generating new state containing desired pid
    noreply, NewState.

但是,我对这个解决方案并不满意。

问题是:根据 OTP 设计原则,监督树成员之间最常规的交互方式是什么?

【问题讨论】:

您能解释一下为什么您的解决方案不能让您满意吗? 如果你打算创建一个应用程序,每个VM只能运行一次,所以在你描述的情况下,我不认为使用本地注册的进程是一个不好的解决方案。 @Pascal,是的,我打算。但是这样的注册不仅会导致“应用程序”实例之间的名称冲突,而且还会增加与其他进程发生名称冲突的可能性。不过,看起来我应该考虑这个解决方案。 @evnu,这个解决方案看起来不太自然和清晰。我专门为在初始化阶段使用一次而创建了新消息。这条消息是通过进程发送给它自己的……奥卡姆在他的坟墓里转身 (en.wikipedia.org/wiki/Occam's_razor)。 为了避免名称冲突我通常选择模块名称作为进程名称,如果我没有模块名称冲突,那么我有它用于进程一的可能性很小。 【参考方案1】:

这在很大程度上取决于您的业务逻辑,但如果担心阻塞全局范围对您来说是有意义的,我的建议是您考虑您正在使用的监督策略。

在这种情况下,当两个对等进程需要以这种方式关联时,我看到的是,创建了一个额外的控制器进程来管理这种关联,并使用 simple_one_for_one sup 来生成两个对等进程之一(最有可能的 gen_server )。当使用进程注册是一个问题时,可以再次使用这种技术,例如当您将有许多运行相同代码的 gen_servers 实例时。

该技术基本上包括使用 simple_one_for_one 主管来生成您的 gen_server。基本上发生的情况是,在启动时(simple_one_for_one sup)不会立即产生任何子进程,而只会在您明确调用 supervisor:start_child(Sup, List) 时产生。

然后您的 child2 初始化逻辑(甚至在某些时候甚至是您的业务逻辑)可以通过 supervisor:start_child(Sup, List) 生成 gen_server 并获得 child2 Pid,以便他们可以轻松地进行通信。

额外的控制器进程保留了您的 gen_server 之间的关系的字典,由一些唯一性标识,但避免了全局空间阻塞,因为这只是控制器的本地。然后,您可以要求您的控制器在您的逻辑上的任何时候分配或取消分配此关系。

因此,监督树看起来像:

您可以在 github 上的 tinymq 项目上使用此实现来阅读代码

如果您以前从未使用过 simple_one_for_one 主管,则子规范可能有点棘手,请记住,您将通过 supervisor:start_child(Sup, List) 上的列表将 child2 的 pid 传递给您的 gen_server,该列表将附加到 Args 之一,您在子规范中指定。

simple_one_for_one supervisors

我的两分钱!

【讨论】:

【参考方案2】:

当然,您不能在主管尚未启动所有子项的情况下向主管询问其子项:)

注册(在本地,使用 erlang:register())实际上并不是一个坏主意。此外,如果在 child1 中处理原始 pid,您应该手动设置对 child2 pid 的监控,以便能够对可能的崩溃等做出反应,但是注册后您只需直接按名称询问即可。

如果不注册,您可以推迟通知孩子,直到调用 supervisor:start_link:

start_link() ->
    R=supervisor:start_link(local, ?SERVER, ?MODULE, []),

    %% Here supervisor is started so you can notify its children
    R.

【讨论】:

可能的崩溃不是问题,因为one_for_all 主管将在其所有子节点崩溃时重新启动它。因此,手动监控并不是真正必要的。当然,如果我们在主管开始时只通知孩子一次,则不会。

以上是关于如何在 Erlang/OTP 中将主管的孩子 pid 共享给另一个孩子的主要内容,如果未能解决你的问题,请参考以下文章

Erlang/OTP 消息可靠吗?消息可以复制吗?

Erlang OTP 应用程序设计

何时在 Erlang/OTP 应用程序中使用 gen_server

您如何设计基于Erlang / OTP的分布式容错多核系统的架构?

如何在Erlang / OTP中构建MQ使用者循环?

您如何设计基于 Erlang/OTP 的分布式容错多核系统的架构?