使用gen_server时,有时候我需要做一个"两阶段初始化"或"分离初始化",如下所示:
a)在gen_server回调模块中init/1,只完成了部分初始化
b)之后,a self() ! init_stage2被称为
c)init/1退货{ok, PartiallyInitializedState}
d)在将来的某个时刻,handle_info/2要求处理init_stage2b)中发送的消息,从而完成启动过程.
我主要关心的是,如果一个根服务器call/ cast/ info点是c之间进行)和d),有可能是与被处理请求PartiallyInitializedState?
根据10.8是否保证了消息接收的顺序?,(引用,下面),这是可能的,(如果我理解正确),但我不能产生失败(c之间的请求)和d)处理部分启动状态)
是的,但只在一个过程中.
如果有一个实时进程并且你发送消息A然后发送消息B,则保证如果消息B到达,则消息A到达它之前.
另一方面,假设进程P,Q和R.P将消息A发送到Q,然后将消息B发送到R.不能保证A在B之前到达.(如果需要,则分布式Erlang会非常困难) !)
下面是我用来尝试在c)和d)之间处理调用的一些代码,但当然失败了,否则,我不会在这里问这个问题.(test:start(20000)如果你感兴趣的话,用来跑)
%% file need_two_stage_init.erl
-module(need_two_stage_init).
-behaviour(gen_server).
-export([start_link/0]).
-export([init/1, terminate/2, code_change/3,
handle_call/3, handle_cast/2, handle_info/2]).
start_link() ->
gen_server:start_link(?MODULE, {}, []).
init({}) ->
self() ! go_to_stage2,
%% init into stage1
{ok, stage1}.
handle_call(_Request, _From, Stage) ->
{reply, Stage, Stage}.
%% upon receiving this directive, go to stage2,
%% in which the gen_server is fully functional
handle_info(go_to_stage2, stage1) ->
{noreply, stage2}.
handle_cast(Request, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ignore.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%% file test.erl
-module(test).
-export([start/1]).
start(Amount) ->
start_collector(Amount), %% report the result
start_many_gens(Amount).
start_collector(Amount) ->
spawn(fun() ->
register(collector, self()),
io:format("collector started, entering receive loop~n"),
loop(Amount)
end).
loop(0) ->
io:format("all terminated~n"),
all_terminated;
loop(N) ->
%% progress report
case N rem 5000 == 0 of
true -> io:format("remaining ~w~n", [N]);
false -> ignore
end,
receive
{not_ok, _} = Msg ->
io:format("======= bad things happened: ~p~n", [Msg]),
loop(N-1);
{ok, _} ->
loop(N-1)
end.
start_one_gens() ->
{ok, Pid} = need_two_stage_init:start_link(),
case gen_server:call(Pid, any) of
stage2 -> ignore;
stage1 -> collector ! {not_ok, Pid}
end,
gen_server:stop(Pid),
collector ! {ok, Pid}.
start_many_gens(Amount) ->
lists:foreach(fun(_) ->
spawn(fun start_one_gens/0)
end, lists:seq(1, Amount)).
Run Code Online (Sandbox Code Playgroud)
编辑再次阅读上面引用的文档,我想我确实误解了它,"如果有一个实时进程并且你发送消息A然后发送消息B,则保证如果消息B到达,则消息A到达它之前." 它没有说谁发了A,谁发了B,我猜这意味着没关系,只要他们被送到同一个进程,在这种情况下,两阶段的init练习是安全的.无论如何,如果一些Erlang/OTP大师可以澄清这一点会很好.
(关于话题,说"Erlang/OTP"感觉就像那些GNU人强迫你说"GNU Linux":-)
编辑2感谢@Dogbert,可以用以下两种方式说明这个问题的简短版本:
1)如果进程向自己发送消息,此消息是否保证同步到达邮箱?
2)或者,让A,B和P为三个不同的进程,A先将MsgA发送给P,然后将B发送给P,是否保证MsgA在MsgB之前到达?
在您的情况下,在gen_server:start_link/3您返回之前不会返回need_two_stage_init:init/1。所以要么need_two_stage_init:start_link/0。这说明你的邮箱里已经有了go_to_stage2。Pid因此,当您不使用注册名称时,除了您的进程调用之外没有人知道您的名称gen_server:start_link/3,但无论如何它都会隐藏在那里直到返回。所以你是安全的,因为没有人可以call,cast或者在不知情的情况下向你发送消息Pid。
顺便说一句,您可以实现类似的效果返回{ok, PartiallyInitializedState, 0}然后timeout处理hanle_info/2。
(题外话,Linux 中的 GNU 背后有一段历史,当时 Linux 是 Linus 和他周围的小社区的作品,GNU 已经建立了一个包含大量用户空间应用程序的庞大项目,所以它们有充分的理由以操作系统的名义被提及,其中包含很多他们的工作。Erlang 是语言,OTP 是实用程序和模块的分发,但它们都是同一组人的工作,所以他们可能会原谅你。)
ad 1)不,不能保证,这是当前实现的一种方式,并且在可预见的将来不太可能改变,因为它简单且强大。当进程向同一虚拟机中的进程发送消息时,它会将消息项复制到单独的堆/环境中,然后自动将消息附加到消息框的链接列表中。我不确定如果进程将消息发送给自身,消息是否会被复制。有一个共享堆实现,它不会复制消息,但这些细节都不会改变这样一个事实:在进程继续其工作之前,该消息已链接到接收者的消息框。
ad 2)首先,你怎么知道A发送消息后B也发送消息?想一想。然后我们可以谈谈 MasgA 和 MsgB。不,不能保证 MsgA 在 MsgB 之前到达,特别是如果 A、B 和 P 分别位于不同的 VM(尤其是不同的计算机)上。保证B在A发送MsgA后发送消息MsgB的唯一方法是在A向P发送MsgA后从A发送MsgC,但即使B在收到MsgC后向P发送MsgB,也不能保证P在之前收到MsgA MsgB。因此,在场景 A 向 P 发送 MsgA,然后向 B 发送 MsgC,B 接收 MsgC,然后向 P 发送 MsgB,您知道 MsgA 在 MsgB 之前发送,但在极少数情况下,当 A、B 和 P 开启时,P 仍然可以在 MsgA 之前收到 MsgB通过网络连接的不同计算机。由于消息发送的实现方式,当 A、B 和 P 在同一 VM 上时,这种情况永远不应该发生。