如果gen_server进程中的init/1函数向自己发送消息,是否保证在任何其他消息之前到达?

Dog*_*Dog 14 concurrency erlang

我偶尔会看到一种模式init/1,即gen_server进程的功能会向自己发送一条消息,表明它应该被初始化.这样做的目的是让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.
Run Code Online (Sandbox Code Playgroud)

该过程假定init在任何其他消息之前将收到消息 - 否则它将崩溃.此过程是否有可能在m消息之前获取init消息?


假设没有进程将消息发送到由此生成的随机pid list_to_pid,因为执行此操作的任何应用程序可能根本不起作用,无论此问题的答案如何.

Vin*_*nod 5

这个问题的理论答案有可能是一个过程,在init消息之前得到的消息?是YES.但实际上(当没有进程正在执行list_to_pid并发送消息时)此过程的答案是NO,前提是gen_server不是注册进程.

这是因为gen_server:start_link的返回确保执行gen_server的回调init.因此,在任何其他进程获得Pid发送消息之前,初始化消息是进程消息队列中的第一条消息.因此,您的进程是安全的,并且在init之前不会收到任何其他消息.

但是对于注册过程也不会这样,因为可能存在一个进程,即使在完成回调init函数之前,也可能使用注册名称向gen_server发送消息.让我们考虑一下这个测试功能.

test() ->
    Times = lists:seq(1,1000),
    spawn(gen_server, start_link,[{local, ?MODULE}, ?MODULE, [], []]),
    [gen_server:cast(?MODULE, No) || No <-Times].
Run Code Online (Sandbox Code Playgroud)

样本输出是

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 
...
Run Code Online (Sandbox Code Playgroud)

您可以看到gen_server在初始化之前收到356到385条消息的消息.因此,异步回调在注册名称方案中不起作用.

这可以通过两种方式解决

1.返回Pid后注册进程.

 start_link_reg() ->
      {ok, Pid} = gen_server:start(?MODULE, [], []),
      register(?MODULE, Pid).
Run Code Online (Sandbox Code Playgroud)

2.或者在handle_cast中为init消息注册该进程.

handle_cast(init, State) ->
    register(?MODULE, self()),
    io:format("Initializing~n"),
    {noreply, State};
Run Code Online (Sandbox Code Playgroud)

此更改后的示例输出为

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
...
Run Code Online (Sandbox Code Playgroud)

因此,向自己发送消息以进行初始化并不能确保它是它收到的第一条消息,但代码(和设计)中的一些变化可以确保它是第一个被执行的消息.


Luk*_*kas 0

gen_server 使用 proc_lib:init_ack 确保进程在从 start_link 返回 pid 之前正确启动。所以 init 中发送的消息将是第一条消息。