【问题标题】:Can receive clause be used in a gen_server process?可以在 gen_server 进程中使用接收子句吗?
【发布时间】:2017-01-14 15:54:21
【问题描述】:

可以在 gen_server 进程中使用receive 子句吗?我正在阅读 Designing for Scalability 的第 10 章,它说:

有什么理由让作者这么说吗?我知道如果我们想与 gen_server 通信,我们应该gen_server:call/cast,但是如果在我们的handle_call/cast 部分,我们需要receive 子句的力量呢?可以用吗?

【问题讨论】:

    标签: erlang erlang-otp gen-server


    【解决方案1】:

    Pascal's answer 告诉你为什么在handle_call 中使用receive 和其他回调可能是也可能不是一个好主意。让我注意到,这似乎不是作者对那句话的意思。需要说明的是,如果您希望有两条消息 A 和 B,并且您不确定哪一条会先到达,但您想在 B 之前处理 A,您可以使用 receive 轻松做到这一点:

    wait_for_a() ->
        receive
            {a, A} ->
                process_a(A),
                wait_for_b()
        end.
    
    wait_for_b() ->
        receive
            {b, B} ->
                process_b(B)
        end.
    

    但是,如果您的进程是 gen_server 或 gen_fsm,您就不能这样做:您的回调函数将按顺序调用传入的消息,如果您想推迟消息 B 以供以后处理,您必须将其保存在您的状态或其他内容中。


    handle_call/handle_cast 中使用receive 时要考虑的另一件事是您可能会收到system messages,理论上您应该准备好处理。

    【讨论】:

    • 再读一遍,我想你是对的,作者不是在说receive,他说的是handle_call,没有使用receive。至于系统消息,我想我不需要担心它们——只要我不在接收子句中进行模式匹配,它们就会留在邮箱中等待其他组件处理。
    • 我是这本书的合著者之一,@legoscia 的解释就是我们的意思。无论如何,@Pascal 和@legoscia 都在这里提供了非常好的建议。不过,我向 OP 建议,与其询问您是否可以在标准行为中使用 receive,不如在一个单独的问题中解释您实际要完成的工作,以便我们可以提供可行的替代方案.
    【解决方案2】:

    默认情况下,gen_server 调用(和 gen_fsm)的超时时间为 5 秒。如果一个回调函数持续时间过长,那么服务器会崩溃,原因

    {timeout,{gen_server,call,[GenServerPid,LastMessage]}}

    看来cast函数的超时时间不一样(我猜是因为回调立即返回),但是如果被回调执行阻塞会导致下一次调用失败。

    所以我认为这不是一个好主意,除非您的应用程序要求在回调期间有消息很快到达(换句话说,如果您认为缺少第二条消息是一种错误情况)

    检查以下代码:

    -module (tout).
    
    -behaviour(gen_server).
    
    -define(SERVER, ?MODULE).
    
    %% export interfaces
    -export([start_link/0,call/2,cast/2]).
    
    %% 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, ?SERVER}, ?MODULE, [], []).
    
    call(Pid,Time) ->
        gen_server:call(Pid,{wait,Time}).
    
    cast(Pid,Time) ->
        gen_server:cast(Pid,{wait,Time}).
    
    %% CALLBACK FUNCTIONS %%
    
    init([]) ->
        {ok, #{}}.
    
    handle_call({wait,Time}, _From, State) ->
        timer:sleep(Time),
        {reply, done, State};
    handle_call(_Request, _From, State) ->
        {reply, {error, unknown_call}, State}.
    
    handle_cast({wait,Time}, State) ->
        timer:sleep(Time),
        {noreply, State};
    handle_cast(_Msg, State) ->
        {noreply, State}.
    
    handle_info(_Info, State) ->
        {noreply, State}.
    
    terminate(_Reason, _State) ->
        ok.
    
    code_change(_OldVsn, State, _Extra) ->
        {ok, State}.
    
    %% LOCAL FUNCTIONS %%
    

    Shell 会话:

    1> c(tout).                     
    {ok,tout}
    2> tout:start_link().           
    {ok,<0.136.0>}
    3> tout:call(tout,500).         
    done
    4> tout:call(tout,5100).        
    ** exception exit: {timeout,{gen_server,call,[tout,{wait,5100}]}}
         in function  gen_server:call/2 (gen_server.erl, line 204)
    5> tout:start_link().   
    {ok,<0.141.0>}
    6> tout:cast(tout,10000).
    ok
    7> tout:cast(tout,1000). 
    ok
    8> % wait a little .
    8> tout:call(tout,100).  
    done
    9> tout:cast(tout,10000). 
    ok
    10> % no wait .
    11> tout:call(tout,100).  
    ** exception exit: {timeout,{gen_server,call,[tout,{wait,100}]}}
         in function  gen_server:call/2 (gen_server.erl, line 204)
    12> 
    

    [编辑] 是的,gen_fsm 无法进行选择性接收,通常的问题是:

    Fsm 处于 state_1 并接收到应该在 state_2 中处理的消息,就在请求转到 state_2 的消息到达之前:

    • 第一条消息必须在 state_1 中处理,否则进程崩溃,
    • 此消息已从邮箱中删除,并且当 fsm 切换到 state2 时不会出现在此处,因此应用程序必须管理此风险(例如,通过单个进程以正确的顺序发送 2 条消息来避免这种情况) .

    这种情况无法通过其中一个回调中的接收块来解决,因为此问题出现在两个回调之间,而进程正在执行 gen_fsm 代码。

    我认为这是 R19 中出现的新行为 gen_statem 应该解决的问题之一(不过我读得很快)

    【讨论】:

    • 在我的例子中,没有调用 gen_server,所有的工作都是通过 cast 完成的。猜猜你对超时的看法是对的,但我认为这不是作者这么说的原因。
    猜你喜欢
    • 2021-06-06
    • 2016-07-19
    • 1970-01-01
    • 2021-04-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-07
    • 1970-01-01
    相关资源
    最近更新 更多