Erlang:两阶段init安全吗?

Not*_* ID 7 erlang erlang-otp

使用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之前到达?

Hyn*_*dil 1

在您的情况下,在gen_server:start_link/3您返回之前不会返回need_two_stage_init:init/1。所以要么need_two_stage_init:start_link/0。这说明你的邮箱里已经有了go_to_stage2Pid因此,当您不使用注册名称时,除了您的进程调用之外没有人知道您的名称gen_server:start_link/3,但无论如何它都会隐藏在那里直到返回。所以你是安全的,因为没有人可以callcast或者在不知情的情况下向你发送消息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 上时,这种情况永远不应该发生。