您如何在 gen_servers 中进行选择性接收?

Posted

技术标签:

【中文标题】您如何在 gen_servers 中进行选择性接收?【英文标题】:How do you do selective receives in gen_servers? 【发布时间】:2010-11-20 09:33:06 【问题描述】:

我将我的大部分应用程序移植到 OTP 行为,但我被卡住了。我不知道如何使用 gen_server 进行选择性接收。如果没有任何回调函数子句匹配一条消息,而不是将消息放回邮箱,它会出错!

现在,无论我走到哪里,人们都称赞选择性接收。无论我走到哪里,人们都会称赞 OTP。不能同时拥有两者真的是真的吗?这似乎不是一个可以纠正的主要缺点吗?

erlang 程序员如何处理这个问题?

编辑(回应zed的评论):

这是一个示例,我希望看到按排序顺序打印的整数列表:

-module(sel_recv).
-behaviour(gen_server).

-export([start_link/0]).

-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
     terminate/2, code_change/3]).

-export([test/0]).

start_link() ->
    gen_server:start_link(local, ?MODULE, ?MODULE, [], []).

test() ->
    gen_server:cast(?MODULE, test).

init([]) ->
    ok, 0.

handle_call(_Request, _From, State) ->
    Reply = ok,
    reply, Reply, State.

handle_cast(test, _State) ->
    lists:map(fun(N) ->
                      gen_server:cast(?MODULE, result, N)
              end, [9,8,7,6,5,4,3,2,1]),
    noreply, [1,2,4,5,6,7,8,9];
handle_cast(result, N, [N|R]) ->
    io:format("result: " ++ integer_to_list(N) ++ "~n"),
    noreply, R.

handle_info(_Info, State) ->
    noreply, State.

terminate(_Reason, _State) ->
    ok.

code_change(_OldVsn, State, _Extra) ->
    ok, State.

当然,在我的真实应用程序中,存在计时器延迟,并且需要按顺序处理的消息与其他消息交错。特别是,我发送 http 请求,有时一次发送多个请求,有时一次发送一个请求,它们之间有间隔。无论如何,我需要按顺序收集它们。

【问题讨论】:

你想达到什么目标? 【参考方案1】:

Gen_server 可能不是最好的选择。 您可以做的一件事是将所有消息接收到缓冲区列表中,然后自己实现选择性接收:

handle_cast(test, _State) ->
    ...
    noreply, [1,2,4,5,6,7,8,9], [];

handle_cast(result, N, Wait, Buff) ->
    noreply, handle_results(Wait, [N|Buff]).

handle_results([], Buff) ->
    [], Buff;

handle_results([W|WTail] = Wait, Buff) ->
    case lists:member(W, Buff) of
        true ->
            io:format("result: " ++ integer_to_list(W) ++ "~n"),
            handle_results(WTail, Buff -- [W]);
        false ->
            Wait, Buff
    end.

【讨论】:

为了完整起见,您可以将所有不需要的邮件重新发送给自己(从而将其移至邮箱末尾)。这有很多问题(您的 gen_server 将在全速来回传递消息时工作;它使用实现细节),因此您永远不应该使用它 :) handle_cast(Msg, State) -> self() ! '$gen_cast', Msg, noreply, State。 是的,我之前考虑过这个。非常简短。 ;) 你的第一个想法是个好主意。通常的情况是我需要在处理它们之前收集 8 个项目。一个工作通常会有 200 批这样的产品。我正在尝试计算您的方法是否比用数字标记每个结果并在收到 8 个项目后对每个批次进行排序更有效。【参考方案2】:

也许你真的想使用 gen_fsm。该行为通常被选择用于协议的前端,其中协议具有某些状态并且需要根据当前所处的状态以不同的方式处理请求。

但回到 gen_server,我们将 gen_server 用于它的功能。它订阅系统事件以进行代码加载,并为您提供 code_change 回调。它会导致有关崩溃的 sasl 报告。您将获得一个有助于代码维护的众所周知的结构。最重要的是,它为同步调用实现了一个精心设计的协议。

在这种情况下,很难说 OTP 行为是否适合您。


鉴于评论,听起来 gen_server 对您来说是错误的。当我使用 gen_server 行为时,我认为它是公司的老板。每个人都想和老板交谈,所以让老板能够快速有效地委派工作,这样他就不会让人们坐等而不是工作,这符合公司的最大利益。

gen_server 可以使用“From”参数进行委托。为此,请返回 no_reply, State 并将 From 参数传递给委托。代理使用gen_server:reply/2 接听原始电话。

这种使用 gen_server 的方法可能适合您。启动一个“普通”过程,您使用接收端来进行选择性接收。如果这是一个真正独立的工作,gen_server 可以忽略它(即开即忘)。如果它想知道它是否已完成,可以gen_server:cast/2 一条消息从已启动的“普通”进程返回给它。

如果您想在执行选择性接收时阻止 gen_server,那么最好也启动一个进程并等待它终止后再返回。选择性接收是按消息到达的顺序对消息进行 O(n) 线性搜索。因此,每个选择性接收都会扫描所有排队的消息,这些消息对于流行的 gen_server 来说可能很高。

没有一家公司应该只有老板在那里工作。任何 erlang 应用程序都不应该只有 gen_servers。

【讨论】:

gen_fsm 是一个有趣的想法——我一直在寻找使用它的借口。乍一看,我不认为它对我有用,除非我能以某种方式“参数化”这些状态。典型情况是 8 个状态,但也有数百个边缘情况。不知道 gen_fsm 是否是为此而生的。我真的非常想要 OTP 功能,所以我会以其他方式处理选择性接收,因为 gen_servers 显然不能自然地做到这一点。【参考方案3】:

仅仅因为您不能将 gen_server 用于您的模块之一并不意味着您没有使用 OTP。所有回调模块都为您实现了接收块,从而阻止您使用选择性接收。您没有理由不能实现自己的处理选择性接收的服务。这样做并不意味着您没有使用 OTP 方式进行操作。

您仍然可以让主管管理您的服务,享受所有优惠。

【讨论】:

【参考方案4】:

不,gen_server 不是为能够处理选择性接收而设计的,每个请求在到达时都会被处理。这实际上是一个难题,因为 Erlang 要求在编译时知道所有模式,没有“模式对象”。

我同意 gen_fsm 可能也不适合您,因为在您获得状态数量爆炸之前,它不需要以任何顺序到达的不同消息。这是我们添加选择性接收的原因之一,它允许您安全地忽略不感兴趣的消息,将它们留到以后。

您对哪个 OTP 特别感兴趣?

【讨论】:

如果您指的是哪些 OTP 功能,我喜欢调试的东西,比如 sys:get_status。这已经很方便了。来自 sasl 的崩溃报告非常宝贵。而且我希望对流程进行监督,尽管这可能与普通流程一样容易?而且我有点喜欢用 gen_server api 调用我的进程的想法。最重要的是,这是我的第一个主要的 erlang 项目,它在截止日期前,OTP 似乎是正确的方式。没有时间探索每一个缝隙,我想确保自己获得所有内置优势。 也就是说,我想尽可能地符合应用程序框架,这样我才能理解有什么优势。另外,我还没有进行版本升级处理,我希望使用 gen_servers 有一个优势。【参考方案5】:

“plain_fsm”将允许您进行选择性接收,同时仍符合 OTP。

http://github.com/esl/plain_fsm

【讨论】:

以上是关于您如何在 gen_servers 中进行选择性接收?的主要内容,如果未能解决你的问题,请参考以下文章

如何接收发送到在 gen_server 内运行的 PID 的消息

如何停止在erlang中作为gen_server实现的tcp_listener

Erlang:如何在主管中正确调度带有 start_child 的 gen_server 并调用 API

如何直观地描述 gen_server?

您如何设计基于Erlang / OTP的分布式容错多核系统的架构?

Erlang:gen_server 还是我自己的自定义服务器?