如何在Erlang/OTP中将Supervisor的子进程ID共享给另一个子进程

7
在我的Erlang/OTP应用程序中,我有一个带有多个子进程的one_for_all监督器(sup)。其中一个子进程(使用gen_server行为的child1)应该能够向另一个子进程(使用supervisor行为的child2)发送消息。当然,我可以注册它,但在全局范围内堆积过多名称似乎不是一个好主意。
因此,使这种交互成为可能的唯一方法是提供child1的pid给child2。说得容易做起来难。有一个具有适当功能的supervisor:which_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设计原则,监管树成员之间的最常规交互方式是什么?


你能解释一下为什么你的解决方案不令你满意吗? - evnu
1
如果您打算创建一个应用程序,它只能在每个虚拟机上运行一次,因此在您描述的情况下,我认为使用本地注册进程并不是一个不好的解决方案。 - Pascal
@Pascal,是的,我打算这样做。但是这种注册不仅会导致“应用程序”实例之间的名称冲突,还会增加与其他进程名称冲突的概率。尽管如此,看起来我应该考虑这个解决方案。 - citxx
你也可以让 supervisor 向新创建的子进程发送消息。只需将调用 gen_server:cast/2 的代码从 init 函数移动到 supervisor 中即可。关于仅用于初始化的消息:可以将其视为状态机。外部输入会使其从“未激活”状态转换为“激活”状态。 - evnu
1
为避免名称冲突,我通常将模块名称选择为进程名称,如果我没有模块名称冲突,那么进程名称的冲突机会就很低。 - Pascal
显示剩余3条评论
2个回答

5

当监督器还没有启动所有子进程时,你不能询问其子进程的情况 :)

实际上,使用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.

2
可能的崩溃不是问题,因为one_for_all监督者会在一个子进程崩溃时重新启动所有子进程。因此,手动监控并不是必要的。当然,如果我们在监督者启动时只通知子进程一次,则无需监控。 - citxx

1

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

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

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

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

额外的控制器进程会保持一个字典,用于记录您的gen_server之间的关系,这些关系由某些唯一标识符确定,但避免了全局空间阻塞,因为这只是控制器的本地内容。然后,您可以在逻辑的任何时候请求您的控制器分配或取消分配此关系。
因此,监督树看起来应该像这样:

supervision tree

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

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

simple_one_for_one supervisors

我的两分钱!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接