我有一个gen_server
进程注册一个全局名称,如下所示:
global:register_name(<<"CLIENT_", NAME/binary>>, self()),
另一个过程是尝试使用gen_server:call
向此过程发送一条消息,如下所示:
gen_server:call({global, <<"CLIENT_", NAME/binary>>}, {msg, DATA}),
如果第二个调用在第一个进程注册全局名称之前发生,它将以:
消亡exit with reason {noproc,{gen_server,call,[{global,<<"CLIENT_122">>},{msg, <<"TEST">>}]}}
仅当全局名称是注册时才进行调用的正确方法是什么,如果不注册则执行其他操作?
答案 0 :(得分:2)
三件事:
<强>力学强>
您可以在拨打电话之前检查名称是否已在全局注册表中注册:
-spec send_message(Name, Message) -> Result
when Name :: term(),
Message :: term(),
Result :: {ok, term()}
| {error, no_proc}.
send_message(Name, Message) ->
case global:whereis_name(Name) of
undefined ->
{error, no_proc};
PID ->
Value = gen_server:call(PID, Message),
{ok, Value}
end.
因为正在检查global:whereis_name/1
的返回值与通过gen_server:call/2,3
的实际通话之间会有几纳秒,所以仍然不知道是否你实际上只是发送了一个死进程的调用,但至少你把它发送到了一个不会立即崩溃程序的PID。
另一种方法是使用try ... catch
构造,但这是一个非常棘手的习惯。
强大的架构
上述所有内容,请将其保留在您的脑海中,但如果此名称未注册,您应该希望崩溃。 你的注册过程应该是活着的,所以你为什么这么偏执?!如果事情很糟糕,你希望知道他们是灾难性的坏事,并让一切与那次坠机有关并立即燃烧。 不要尝试在未知状态下自行恢复,这就是主管的目的。让您的系统在已知状态重新启动并再次启动。如果这是用户指导的操作(系统的某个用户,或网页请求或其他),他们将再次尝试,因为他们是不止一次尝试的猴子。如果它是一个自动请求(例如,用户是计算机或机器人),它可以再次重试或不重试,但在常见情况下将该决定留给它 - 但是给它一些失败的指示(错误消息,一个封闭的插座等。)。
只要您正在调用的进程在其init/1
调用期间(在将其自己的PID返回给其主管之前)注册其名称,并且在调用进程处于活动状态之前始终发生或知道要调用的进程然后你不应该有任何麻烦。如果由于某种原因它已经崩溃,那么你的程序会遇到更多基本问题,并且捕获调用者的崩溃对你没有帮助。这是稳健性工程的基本思想。
构建你的系统,以便在呼叫发生之前保证被呼叫者处于活动状态并注册,如果它已经死亡,你应该想要呼叫者也死掉。 (是的,我正在打死马,但这很重要。)
代码结构
大多数情况下,您不希望拥有定义流程的模块,假设foo.erl
定义了我们将命名为{global, "foo"}
的流程,请对{{1}进行裸露调用或gen_server:call/2,3
用于另一个模块中定义的单独进程(假设gen_server:cast/2
定义了一个我们将命名为bar.erl
的进程)。我们想要的是{global, "bar"}
有一个它导出的接口函数,而且这个函数是bar.erl
但发生的地方。
这样,适用于此调用的任何特殊工作(任何其他调用模块也可能需要)都存在于一个位置,您可以通过传达某些含义的方式将进程命名为进程gen_server:call/2
除了传递给它的消息。
例如,如果"bar"
定义的进程是连接计数器(也许我们正在编写游戏服务器而我们正在计算连接数),我们可能会bar.erl
负责维护计数器。因此,只要新用户连接,进程就会向bar.erl
发送cast
(异步消息)。而不是让每个可能需要执行此操作的不同进程定义一些复杂的名称检查,然后是裸信息发送,而是考虑从bar
导出一个隐藏该混乱的函数,并将其命名为有意义的内容,如{{1} }。只是在你的其他代码中调用它更容易理解,你可以选择如何处理这个“如果栏不存在怎么办?”那里的情况,在一个地方。
在这方面,你可能想看一下基本的Erlang "service manager -> worker" pattern。在许多情况下,并不是绝对需要命名过程。