Concurrency 如果gen_服务器进程中的init/1函数向自身发送一条消息,是否保证它在任何其他消息之前到达?

Concurrency 如果gen_服务器进程中的init/1函数向自身发送一条消息,是否保证它在任何其他消息之前到达?,concurrency,erlang,Concurrency,Erlang,我偶尔会看到一种模式,gen_服务器的init/1函数将向自身发送一条消息,表示应该初始化它。这样做的目的是让gen_server进程异步初始化自身,以便生成它的进程不必等待。以下是一个例子: -module(test). -compile(export_all). init([]) -> gen_server:cast(self(), init), {ok, {}}. handle_cast(init, {}) -> io:format("initial

我偶尔会看到一种模式,
gen_服务器的
init/1
函数将向自身发送一条消息,表示应该初始化它。这样做的目的是让
gen_server
进程异步初始化自身,以便生成它的进程不必等待。以下是一个例子:

-module(test).
-compile(export_all).

init([]) ->
    gen_server:cast(self(), init),
    {ok, {}}.

handle_cast(init, {}) ->
    io:format("initializing~n"),
    {noreply, lists:sum(lists:seq(1,10000000))};
handle_cast(m, X) when is_integer(X) ->
    io:format("got m. X: ~p~n", [X]),
    {noreply, X}.

b() ->
    receive P -> {} end,
    gen_server:cast(P, m),
    b().

test() ->
    B = spawn(fun test:b/0),
    {ok, A} = gen_server:start_link(test,[],[]),
    B ! A.
该过程假定
init
消息将在任何其他消息之前被接收,否则它将崩溃。此过程是否可能在
init
消息之前获取
m
消息



让我们假设没有进程将消息发送到由
list\u to\u pid
生成的随机pid,因为不管这个问题的答案如何,任何这样做的应用程序都可能根本无法工作。

gen\u server使用proc\u lib:init\u ack确保进程在从start\u链接返回pid之前正确启动。因此,在init中发送的消息将是第一条消息

在这种特殊情况下,您可以安全地假设“init”消息将在“m”之前收到。一般来说(尤其是如果您注册了流程),这是不正确的

如果您想100%安全地知道您的初始化代码将首先运行,您可以执行以下操作:

start_link(Args...) ->
    gen_server:start_link(test, [self(), Args...], []).

init([Parent, Args...]) ->
    do_your_synchronous_start_stuff_here,
    proc_lib:init_ack(Parent, {ok, self()}),
    do_your_async_initializing_here,
    io:format("initializing~n"),
    {ok, State}.

我没有对此进行测试,所以我不知道“奖金”初始确认是否会向终端打印丑陋的消息。如果是这样,代码必须稍微扩展,但总体思路仍然有效。让我知道,我会更新我的答案。

这不是100%安全! 在
gen.erl
第117-129行中,我们可以看到:

init_it(GenMod, Starter, Parent, Mod, Args, Options) ->
init_it2(GenMod, Starter, Parent, self(), Mod, Args, Options).

init_it(GenMod, Starter, Parent, Name, Mod, Args, Options) ->
    case name_register(Name) of
        true ->
            init_it2(GenMod, Starter, Parent, Name, Mod, Args, Options);
        {false, Pid} ->
            proc_lib:init_ack(Starter, {error, {already_started, Pid}})
    end.

init_it2(GenMod, Starter, Parent, Name, Mod, Args, Options) ->
    GenMod:init_it(Starter, Parent, Name, Mod, Args, Options).
init_it/7
中,进程首先注册其名称,然后在
init_it2/7
中调用
GenMod:init_it/6
,在其中调用您的
init/1
函数

test() ->
    Times = lists:seq(1,1000),
    spawn(gen_server, start_link,[{local, ?MODULE}, ?MODULE, [], []]),
    [gen_server:cast(?MODULE, No) || No <-Times].
虽然在gen_server:start_链接返回之前,很难猜出新的进程id。但是,如果您向服务器发送一条带有注册名称的消息,并且该消息在调用gen_server:cast之前到达,那么您的代码将是错误的

Daniel的解决方案可能是正确的,但我不确定两个
proc_lib:init_ack
是否会导致错误。但是,家长永远不希望收到意外消息。>_

我是这里的一名新生,我多么希望能添加一条评论。>“您的示例代码是安全的,
m
总是在
init
之后收到

但是,从理论上看,如果gen_服务器的
init/1
处理程序使用
gen_服务器:cast/2
或send原语向自身发送消息,则不能保证它是第一条消息

无法保证这一点,因为
init/1
是在gen_服务器的进程内执行的,因此在创建进程并分配pid和邮箱之后。在非SMP模式下,调度程序可以在调用init函数之前或消息发送之前,在一定负载下调度进程已发送,因为调用函数(例如
gen_server:cast/2
或该问题的init处理程序)会生成一个缩减,并且BEAM emulator会测试是否应该给其他进程一些时间。在SMP模式下,您可以让另一个调度程序运行一些代码,并向您的进程发送消息

理论与实践的区别在于找出进程存在的方式(以便在
init
消息之前向其发送消息)。代码可以使用来自主管的链接、注册名称、由
erlang:processs()返回的进程列表
甚至可以使用随机值调用
list_to_pid/1
,或者使用
binary_to_term/1
取消pid序列化。您的节点甚至可能会从另一个具有序列化pid的节点收到消息,特别是考虑到创建编号在3之后(请参阅其他问题)

这在实践中不太可能。因此,从实际的角度来看,每次使用此模式时,代码都可以设计为确保首先接收
init
消息,并且在服务器接收其他消息之前初始化服务器

如果gen_服务器是一个已注册的进程,您可以从一个管理器启动它,并确保所有客户端随后在管理树中启动,或者引入某种(可能较差的)同步机制。即使您不使用这种异步初始化模式,这也是必需的(否则客户端无法访问服务器)。当然,如果此gen_服务器崩溃和重新启动,您可能仍然会遇到问题,但无论在何种情况下都是如此,您只能通过精心编制的监控树来保存


如果gen_服务器未注册或未按名称引用,客户端最终将pid传递到
gen_服务器:call/2,3
gen_服务器:cast/2
,它们将通过调用
gen_服务器:start_link/3
gen_服务器:start_link/3
的主管获得该pid,仅在
init>时返回/1
返回,因此在
init
消息排队后返回。这正是您上面的代码所做的。

问题的理论答案a进程是否可以在init消息之前获取消息?isYES。 但实际上
1> async_init:test().
Received:356
Received:357
[ok,ok,ok,ok,ok,ok,ok,ok,ok,ok,ok,ok,ok,ok,ok,ok,ok,ok,ok,
 ok,ok,ok,ok,ok,ok,ok,ok,ok,ok|...]
Received:358
Received:359
2> Received:360
2> Received:361
...
2> Received:384
2> Received:385
2> Initializing
2> Received:386
2> Received:387
2> Received:388
2> Received:389 
...
 start_link_reg() ->
      {ok, Pid} = gen_server:start(?MODULE, [], []),
      register(?MODULE, Pid).
handle_cast(init, State) ->
    register(?MODULE, self()),
    io:format("Initializing~n"),
    {noreply, State};
1> async_init:test().
Initializing
Received:918
Received:919
[ok,ok,ok,ok,ok,ok,ok,ok,ok,ok,ok,ok,ok,ok,ok,ok,ok,ok,ok,
 ok,ok,ok,ok,ok,ok,ok,ok,ok,ok|...]
Received:920
2> Received:921
2> Received:922
...