我见过的一种方法是使用 timeoutinit/1
和handle_info/2
.
init(Args) ->
{ok, {timeout_init, Args} = _State, 0 = _Timeout}.
...
handle_info( timeout, {timeout_init, Args}) ->
%% do your inicialization
{noreply, ActualServerState}; % this time no need for timeout
handle_info( ....
几乎所有结果都可以通过额外的超时参数返回,这基本上是等待另一条消息的时间。它给定时间流逝,handle_info/2
被调用,具有timeout
原子和服务器状态。在我们的例子中,超时等于 0,超时甚至应该在gen_server:start
完成之前发生。这意味着handle_info
甚至在我们能够将服务器的 pid 返回给其他任何人之前就应该调用它。所以这timeout_init
应该是第一次调用我们的服务器,并给我们一些保证,我们在处理其他任何事情之前完成初始化。
如果您不喜欢这种方法(不是真的可读),您可以尝试向 self 发送消息init/1
init(Args) ->
self() ! {finish_init, Args},
{ok, no_state_yet}.
...
handle_info({finish_init, Args} = _Message, no_state_yet) ->
%% finish whateva
{noreply, ActualServerState};
handle_info( ... % other clauses
同样,您要确保尽快将完成初始化的消息发送到该服务器,这对于在某个原子下注册的 gen_servers 非常重要。
编辑 在对 OTP 源代码进行了更仔细的研究之后。
当您通过它的 pid 与服务器通信时,这种方法已经足够好了。主要是因为 pid 在您的init/1
函数返回后返回。gen_..
但是在开始start/4
或start_link/4
我们自动以相同名称注册进程的情况下略有不同。您可能会遇到一种竞争条件,我想更详细地解释一下。
如果进程是注册的,通常会简化所有调用并转换为服务器,例如:
count() ->
gen_server:cast(?SERVER, count).
?SERVER
通常模块名称(原子)在哪里,并且在这个名称下是一些注册的(和活动的)进程之前,它可以正常工作。当然,在底层,这cast
是标准 Erlang 的消息发送!
。它没有什么神奇之处,几乎和你在init
with中所做的一样self() ! {finish ...
。
但在我们的例子中,我们假设还有一件事。不仅仅是注册部分,还有我们的服务器完成了它的初始化。当然,由于我们正在处理消息框,因此需要多长时间并不重要,重要的是我们先收到哪条消息。所以确切地说,我们希望在接收finish_init
消息之前接收count
消息。
不幸的是,这种情况可能会发生。这是因为在调用回调之前注册gen
了 OTP 中的 ' 。所以理论上,当一个进程调用将进入注册部分的函数时,另一个进程可以找到我们的服务器并发送消息,然后就可以使用消息调用该函数。机会很小(非常非常小),但它仍然可能发生。 init/1
start
count
init/1
finish_init
对此有三种解决方案。
首先是什么都不做。在这种竞争条件下,handle_cast
将会失败(由于函数子句,因为我们的状态是not_state_yet
原子的),并且主管将重新启动整个事情。
第二种情况是忽略这个坏消息/状态事件。这很容易实现
... ;
handle_cast( _, State) ->
{noreply, State}.
作为你的最后一个条款。不幸的是,大多数使用模板的人都使用这种不幸的(恕我直言)模式。
在这两种情况下,您可能会丢失一条count
消息。如果这确实是一个问题,您仍然可以尝试通过将最后一个子句更改为来解决它
... ;
handle_cast(Message, no_state_yet) ->
gen_server:cast( ?SERVER, Message),
{noreply, no_state_yet}.
但这还有其他明显的优势,我更喜欢“让它失败”的方法。
第三个选项是稍后注册过程。start/4
与其使用并要求自动注册,不如使用start/3
、接收 pid 并自己注册。
start(Args) ->
{ok, Pid} = gen_server:start(?MODULE, Args, []),
register(?SERVER, Pid),
{ok, Pid}.
这样,我们finish_init
在注册之前发送消息,并且在其他任何人可以发送消息之前发送count
消息。
但是这种方法有其自身的缺点,主要是注册本身可能会以几种不同的方式失败。人们总是可以检查OTP 如何处理它,并复制此代码。但这是另一个故事。
所以最终这一切都取决于你需要什么,甚至你在生产中会遇到什么问题。重要的是要知道可能会发生什么坏事,但我个人不会尝试解决任何问题,直到我真正遭受这种竞争状况的影响。