OTP - 同步和异步消息传递

sfi*_*nie 16 erlang

免责声明:erlang新手.

最初吸引我到erlang的一件事是演员模型; 不同进程同时运行并通过异步消息进行交互的想法.

我刚刚开始考虑OTP,特别是看gen_server.我见过的所有示例 - 并且授予它们是教程类型示例 - 使用handle_call()而不是handle_cast()实现模块行为.

我觉得有点混乱.据我所知,handle_call是一个同步操作:调用者被阻塞,直到被调用者完成并返回.这似乎与异步消息传递哲学背道而驰.

我即将开始一个新的OTP应用程序.这似乎是一个基本的架构决策,所以我想在开始之前确定我理解.

具体来说,我的问题是:

  1. 在实际操作中,人们倾向于使用handle_call而不是handle_cast吗?
  2. 如果是这样,当多个客户端可以调用相同的进程/模块时,可扩展性的影响是什么?

谢谢.

Ada*_*erg 23

  1. 取决于你的情况.

    如果你想得到一个结果,handle_call真的很常见.如果您对通话结果不感兴趣,请使用handle_cast.当handle_call使用时,主叫方将阻止,是的.这大部分时间还可以.我们来看一个例子吧.

    如果您有一个Web服务器,它将文件的内容返回给客户端,您将能够处理多个客户端.每个客户端必须等待读取文件的内容,因此handle_call在这种情况下使用将是完全正常的(抛开愚蠢的例子).

    当您确实需要发送请求,执行其他处理然后稍后获得回复的行为时,通常会使用两个调用(例如,一个强制转换和一个调用来获取结果)或正常的消息传递.但这是一个相当罕见的案例.

  2. 使用handle_call将阻止呼叫持续时间的过程.这将导致客户排队获得他们的回复,因此整个事情将按顺序运行.

    如果需要并行代码,则必须编写并行代码.唯一的方法是运行多个进程.

所以,总结一下:

  • 使用handle_call将阻止呼叫者并占用呼叫持续时间内调用的进程.
  • 如果您希望继续并行活动,则必须进行并行化.唯一的方法是启动更多进程,突然调用vs cast不再是一个大问题(事实上,它更适合调用).

  • 谢谢亚当,这真的很有帮助.+1. (2认同)

Asi*_*san 11

亚当的答案很棒,但我有一点要补充

使用handle_call将阻止调用期间的进程.

对于进行handle_call调用的客户端始终如此.这让我花了一些时间来解决这个问题,但这并不一定意味着gen_server在回答handle_call时也必须阻止.

在我的情况下,当我创建一个数据库处理gen_server并故意编写一个执行的查询时,我遇到了这个问题SELECT pg_sleep(10),这个问题是PostgreSQL代表"睡眠10秒",这是我测试非常昂贵的查询的方法.我的挑战:我不希望数据库gen_server坐在那里等待数据库完成!

我的解决方案是使用gen_server:reply/2:

当无法在Module:handle_call/3的返回值中定义回复时,gen_server可以使用此函数将回复显式发送到调用call/2,3或multi_call/2,3,4的客户端.

在代码中:

-module(database_server).
-behaviour(gen_server).
-define(DB_TIMEOUT, 30000).

<snip>

get_very_expensive_document(DocumentId) ->
    gen_server:call(?MODULE, {get_very_expensive_document, DocumentId}, ?DB_TIMEOUT).    

<snip>

handle_call({get_very_expensive_document, DocumentId}, From, State) ->     
    %% Spawn a new process to perform the query.  Give it From,
    %% which is the PID of the caller.
    proc_lib:spawn_link(?MODULE, query_get_very_expensive_document, [From, DocumentId]),    

    %% This gen_server process couldn't care less about the query
    %% any more!  It's up to the spawned process now.
    {noreply, State};        

<snip>

query_get_very_expensive_document(From, DocumentId) ->
    %% Reference: http://www.erlang.org/doc/man/proc_lib.html#init_ack-1
    proc_lib:init_ack(ok),

    Result = query(pgsql_pool, "SELECT pg_sleep(10);", []),
    gen_server:reply(From, {return_query, ok, Result}).
Run Code Online (Sandbox Code Playgroud)