使用gen_server时,有时我需要做一个"两阶段初始化"或者"分离的init",它是这样的:
a)在gen_server回调模块的init/1
中,只完成了部分初始化
b)之后,self() ! init_stage2
被称为
c)init/1
返回{ok, PartiallyInitializedState}
d)在将来的某个时刻,调用handle_info/2
来处理b)中发送的init_stage2
消息,从而完成启动过程。
我的主要关注点是,如果在c)和d)之间建立了发电服务器call
/ cast
/ info
,是否可以请求使用PartiallyInitializedState
处理?
根据10.8 Is the order of message reception guaranteed?,(引用,如下),这是可能的,(如果我理解的话),但我不能产生失败(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)).
编辑再次阅读上面引用的文档,我想我确实误解了它,"如果有一个实时进程,你发送消息A然后发送消息B,它& #39; s保证如果消息B到达,消息A就会到达它。" 它没有说谁发送了A,谁发送了B,我想这意味着它没有&#39重要的是,只要他们被送到同一个过程,在这种情况下,两阶段的初始练习是安全的。无论如何,如果一些Erlang / OTP大师可以澄清这一点,那就太好了。
(关于主题,说" Erlang / OTP"感觉就像那些GNU人强迫你说'#34; GNU Linux": - )
编辑2 感谢@Dogbert,可以通过以下两种方式说明此问题的简短版本:
1)如果进程向自己发送消息,此消息是否可以保证同步到达邮箱?
2)或者,让A,B和P为三个不同的进程,A先将MsgA发送给P,然后将M发送给P,是否保证MsgA在MsgB之前到达?
答案 0 :(得分:1)
在您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是Linus的工作和他周围的小社区时,Linux中有GNU背后的历史,GNU已经建立了大量用户空间应用程序的大项目,所以他们有充分的理由以OS的名义提及包括很多他们的工作.Erlang是语言,OTP是实用程序和模块的分发,但它们都是同一群人的工作,所以他们可能会原谅你。)
ad 1)不,这不是保证,它是当前实施的一种方式,并且在预见的未来不太可能改变,因为它简单而强大。当进程将消息发送到同一VM中的进程时,它会将消息术语复制到分离的堆/环境中,然后以原子方式将消息附加到消息框的链接列表中。如果进程将消息发送给自己,我不确定是否复制了消息。有共享堆实现,它不复制消息,但这些细节都没有改变这一事实,该消息在进程继续工作之前链接到接收者的消息框。
ad 2)首先,您知道B如何在A发送消息后发送消息?想一想。然后我们可以谈谈MasgA和MsgB。不能保证MsgA在MsgB之前到达,特别是如果A,B和P各自在不同的VM上,尤其是不同的计算机上。 A发送MsgA后保证B发送消息MsgB的唯一方法是在A向Ms发送MsgA后从A发送MsgC,但即使B在接收到MsgC后将MsgB发送给P,也不能保证P之前收到MsgA MSGB。所以在场景A中发送MsgA到P然后MsgC发送到B和B接收MsgC然后发送MsgB到P你知道在MsgB之前发送了MsgA但是在极少数情况下P仍然可以在MsgA之前收到MsgB,当A,B和P打开时由网络连接的不同计算机。当A,B和P在同一个虚拟机上时,它应该永远不会发生,这是由于如何实现消息发送。
答案 1 :(得分:0)
可以在两个步骤之间接收消息,即使它不太可能,特别是如果服务器没有注册(因为另一个进程很少有机会使用服务器的pid),但是注册过程它例如,当主管在应用程序的某个其他部分重新启动服务器时。为了展示它的实际应用,我写了一个小例子,系统地失败了(当作为测试(坏)启动时)。
为了实现它,我在init函数中插入了一个100ms的睡眠。它为另一个进程提供了在初始化时向服务器发送消息的时间,并且可以模拟一个长初始化函数。
我使用第二个进程先生成并每隔10ms向服务器发送一条消息。该消息使用已注册的名称,因此我不需要知道pid,它是在catch块中发送的,因为由于服务器尚未注册,所以第一个消息发送将会失败。
-module(twosteps).
-behaviour(gen_server).
%% export interfaces
-export([start_link/0,test/1,stop/0,badguy/0]).
%% export callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
%% INTERFACES %%
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
stop() ->
gen_server:stop(?MODULE).
test(good) ->
start_link(),
?MODULE ! this_will_work,
stop();
test(bad) ->
Pid = spawn(?MODULE,badguy,[]),
start_link(),
stop(),
Pid ! stop.
%% CALLBACKS %%
init(_) ->
timer:sleep(100),
self() ! finish_init,
{ok, first_step}.
handle_call(_Request, _From, State) ->
{reply, {error, unknown_call}, State}.
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info(finish_init, first_step) ->
io:format("Init OK~n"),
{noreply, init_done};
handle_info(finish_init, too_late) ->
io:format("iznogoud~n"),
{noreply, too_late};
handle_info(_Info, first_step) ->
io:format("Init KO, state is first_step~n"),
{noreply, too_late};
handle_info(_Info, State) ->
io:format("State is ~p~n",[State]),
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%% LOCAL FUNCTIONS %%
badguy() ->
catch (?MODULE ! this_will_fail),
receive
stop -> ok
after 10 ->
badguy()
end.
行动中:
1> c(twosteps).
{ok,twosteps}
2> twosteps:test(good).
Init OK
State is init_done
ok
3> twosteps:test(bad).
Init KO, state is first_step
State is too_late
State is too_late
State is too_late
State is too_late
State is too_late
iznogoud
State is too_late
State is too_late
stop
4>