Erlang/OTP:同步与异步消息传递
Erlang 最初吸引我的地方之一就是 Actor 模型;不同进程同时运行并通过异步消息传递进行交互的想法。
我刚刚开始接触 OTP,特别是关注 gen_server。我见过的所有示例 - 并且假定它们是教程类型的示例 - 使用 handle_call()
而不是 handle_cast()
来实现模块行为。
我觉得这有点令人困惑。据我所知,handle_call 是一个同步操作:调用者被阻塞,直到被调用者完成并返回。这似乎与异步消息传递哲学背道而驰。
我即将开始新的 OTP 申请。这似乎是一个基本的架构决策,所以我想在开始之前确保我理解。
我的问题是:
- 在实际实践中,人们是否倾向于使用
handle_call
而不是handle_cast
? - 如果是这样,当多个客户端可以调用相同的进程/模块时,可扩展性有何影响?
One of the things that attracted me to Erlang in the first place is the Actor model; the idea that different processes run concurrently and interact via asynchronous messaging.
I'm just starting to get my teeth into OTP and in particular looking at gen_server. All the examples I've seen - and granted they are tutorial type examples - use handle_call()
rather than handle_cast()
to implement module behaviour.
I find that a little confusing. As far as I can tell, handle_call
is a synchronous operation: the caller is blocked until the callee completes and returns. Which seems to run counter to the async message passing philosophy.
I'm about to start a new OTP application. This seems like a fundamental architectural decision so I want to be sure I understand before embarking.
My questions are:
- In real practice do people tend to use
handle_call
rather thanhandle_cast
? - If so, what's the scalability impact when multiple clients can call the same process/module?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
取决于您的情况。
如果你想得到结果,
handle_call
确实很常见。如果您对调用结果不感兴趣,请使用handle_cast
。当使用handle_call
时,调用者会阻塞,是的。这大部分时间都可以。让我们看一个例子。如果您有一个将文件内容返回给客户端的 Web 服务器,您将能够处理多个客户端。每个客户端必须等待读取文件内容,因此在这种情况下使用
handle_call
就完全没问题(除了愚蠢的例子)。当您确实需要发送请求、进行一些其他处理然后稍后获取回复的行为时,通常会使用两个调用(例如,一个强制转换和一个调用来获取结果)或正常的消息传递。但这是相当罕见的情况。
使用
handle_call
将在调用期间阻塞进程。这将导致客户端排队等待回复,因此整个过程将按顺序运行。如果你想要并行代码,你就必须编写并行代码。唯一的方法是运行多个进程。
因此,总结一下:
handle_call
将阻塞调用者并在调用期间占用被调用的进程。Depends on your situation.
If you want to get a result,
handle_call
is really common. If you're not interested in the result of the call, usehandle_cast
. Whenhandle_call
is used, the caller will block, yes. This is most of time okay. Let's take a look at an example.If you have a web server, that returns contents of files to clients, you'll be able to handle multiple clients. Each client have to wait for the contents of files to be read, so using
handle_call
in such a scenario would be perfectly fine (stupid example aside).When you really need the behavior of sending a request, doing some other processing and then getting the reply later, typically two calls are used (for example, one cast and the one call to get the result) or normal message passing. But this is a fairly rare case.
Using
handle_call
will block the process for the duration of the call. This will lead to clients queuing up to get their replies and thus the whole thing will run in sequence.If you want parallel code, you have to write parallel code. The only way to do that is to run multiple processes.
So, to summarize:
handle_call
will block the caller and occupy the process called for the duration of the call.Adam的回答很好,但我有一点要补充
对于发出handle_call 调用的客户端始终如此。这花了我一段时间来思考,但这并不一定意味着 gen_server 在应答 handle_call 时也必须阻塞。
就我而言,当我创建一个处理 gen_server 的数据库并故意编写一个执行 SELECT pg_sleep(10) 的查询时遇到了这种情况,这是 PostgreSQL 所说的“睡眠 10 秒”,并且是我的测试非常昂贵的查询的方法。我的挑战:我不希望数据库 gen_server 坐在那里等待数据库完成!
我的解决方案是使用 gen_server:reply/2:
在代码中:
Adam's answer is great, but I have one point to add
This is always true for the client who made the handle_call call. This took me a while to wrap my head around but this doesn't necessarily mean the gen_server also has to block when answering the handle_call.
In my case, I encountered this when I created a database handling gen_server and deliberately wrote a query that executed
SELECT pg_sleep(10)
, which is PostgreSQL-speak for "sleep for 10 seconds", and was my way of testing for very expensive queries. My challenge: I don't want the database gen_server to sit there waiting for the database to finish!My solution was to use gen_server:reply/2:
In code:
IMO,在并发世界中
handle_call
通常是一个坏主意。假设我们有进程 A (gen_server) 接收一些事件(用户按下按钮),然后将消息传递给进程 B (gen_server),请求对按下的按钮进行大量处理。进程 B 可以生成子进程 C,后者在准备好时将消息投射回 A(然后将消息投射回 A)。在处理期间,A 和 B 都准备好接受新请求。例如,当 A 收到来自 C(或 B)的投射消息时,它会向用户显示结果。当然,第二个按钮可能会在第一个按钮之前被处理,因此 A 可能应该以正确的顺序累积结果。通过handle_call
阻塞A和B将使该系统成为单线程(尽管会解决排序问题)。事实上,生成C与
handle_call
类似,不同的是C是高度专业化,仅处理“一条消息”,然后退出。 B 应该具有其他功能(例如限制工作人员数量、控制超时),否则可以从 A 生成 C。编辑:C 也是异步的,因此生成 C 它与
handle_call
不相似( B 未被阻止)。IMO, in concurrent world
handle_call
is generally a bad idea. Say we have process A (gen_server) receiving some event (user pressed a button), and then casting message to process B (gen_server) requesting heavy processing of this pressed button. Process B can spawn sub-process C, which in turn cast message back to A when ready (of to B which cast message to A then). During processing time both A and B are ready to accept new requests. When A receives cast message from C (or B) it e.g. displays result to the user. Of course, it is possible that second button will be processed before first, so A should probably accumulate results in proper order. Blocking A and B throughhandle_call
will make this system single-threaded (though will solve ordering problem)In fact, spawning C is similar to
handle_call
, the difference is that C is highly specialized, process just "one message" and exits after that. B is supposed to have other functionality (e.g. limit number of workers, control timeouts), otherwise C could be spawned from A.Edit: C is asynchronous also, so spawning C it is not similar to
handle_call
(B is not blocked).有两种方法可以解决这个问题。一是改用事件管理方法。我正在使用的方法是使用如图所示的强制转换...
强制转换/提交代码是...
该引用用于异步跟踪查询。
There are two ways to go with this. One is to change to using an event management approach. The one I am using is to use cast as shown...
And the cast/submit code is...
The reference is used to track the query asynchronously.