同步与异步消息

免责声明:erlang新手。

首先吸引我去erlang的其中一件事就是Actor模型; 不同进程同时运行并通过异步消息传递的思想。

我刚刚开始接受OTP并特别关注gen_server。 我见过的所有示例(并授予它们为教程类型示例)都使用handle_call()而不是handle_cast()来实现模块行为。

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

我即将开始一个新的OTP应用程序。 这看起来像是一个基本的体系结构决定,所以我想在开始之前确定我的理解。

具体来说,我的问题是:

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


  • 取决于你的情况。

    如果你想得到结果, handle_call真的很常见。 如果您对调用的结果不感兴趣,请使用handle_cast 。 当handle_call ,调用者会阻止,是的。 这是最好的时间。 我们来看一个例子。

    如果您有一台Web服务器,它将文件内容返回给客户端,那么您将能够处理多个客户端。 每个客户端都必须等待文件的内容被读取,所以在这种情况下使用handle_call将会非常好(抛开愚蠢的例子)。

    当你真的需要发送请求,做一些其他处理然后得到答复的行为时,通常会使用两个调用(例如,一个强制转换和一个调用来获得结果)或正常的消息传递。 但这是一个相当罕见的情况。

  • 使用handle_call将在通话期间阻止进程。 这将导致客户排队等待他们的回复,因此整个事情将按顺序运行。

    如果你想要并行代码,你必须编写并行代码。 唯一的方法就是运行多个进程。

  • 所以,总结一下:

  • 使用handle_call将阻止呼叫者并占用在呼叫期间呼叫的过程。
  • 如果你想要平行活动继续下去,你必须并行化。 要做到这一点的唯一方法是启动更多的流程,并且突然调用vs cast不再是一个大问题(实际上,它更适合调用)。

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

    使用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}).
    

    国际海事组织,在并发世界handle_call通常是一个坏主意。 假设我们有进程A(gen_server)接收到某个事件(用户按下了一个按钮),然后将消息强制转换到进程B(gen_server),请求重复处理此按钮。 进程B可以产生子进程C,然后在准备就绪时将消息投递回给A(即将消息投递给A的B)。 在处理期间,A和B都准备好接受新的请求。 当A从C(或B)接收到投射信息时,它将结果显示给用户。 当然,第二个按钮在第一个按钮之前可能会被处理,所以A应该可能以正确的顺序累加结果。 通过handle_call阻止A和B将使这个系统成为单线程(虽然会解决排序问题)

    实际上,产卵C与handle_call类似,区别在于C是高度专业化的,只处理“一条消息”并在此之后退出。 B应该具有其他功能(例如,限制工人数量,控制超时),否则C可以从A产生。

    编辑:C也是异步的,所以产生C它不像handle_call (B没有被阻塞)。

    链接地址: http://www.djcxy.com/p/38197.html

    上一篇: Synchronous vs. Asynchronous messaging

    下一篇: Erlang/OTP behaviors for beginner