我需要根据需要生成相同gen_fsm的几个独立实例,然后能够将调用路由到正确的实例。
Gproc库似乎是一种使用任意名称注册进程的好方法。它具有一个函数gproc:reg_or_locate/3,用于在没有竞争条件的情况下按需生成东西。这样我甚至不需要主管 - 如果他们崩溃他们将再次按需生成。但我无法弄清楚如何应用gproc:reg_or_locate/3
来生成gen_fsm或gen_server。
到目前为止我尝试了什么:
我只是通过该函数调用gen_server:start(),它将创建一个中间进程,给中间进程命名,中间进程将生成gen_server并终止,最后我得到一个无名的gen_server。 / p>
gen_server和gen_fsm都会导出一个enter_loop函数,如果我将其提供给gproc:reg_or_locate/3
,它似乎可以满足我的需要,但文档中写着:
已使用其中一个启动函数启动必须的过程 proc_lib,请参阅proc_lib(3)。
gproc:reg_or_locate/3
的文档没有提到他们通过proc_lib做任何事情。
或者我可以让中间过程获取名称,然后将其原子地转移到它生成的gen_server或gen_fsm,但是这会产生竞争条件:中间过程将具有gen_fsm的名称,并且任何用于gen_fsm的消息都会出现到中间过程并迷路。
我觉得我在这里缺少一些简单的东西。这不是一个不常见的模式,所以应该有一个很好的方法来做到这一点。我错过了什么?
答案 0 :(得分:3)
出于您的目的,我不认为gproc:reg_or_locate/3
确实为您提供了有用的东西。如果它返回一个PID(由于产生一个新进程,或者找到一个现有的进程),那么在你向它发送一条消息之前,进程仍然会死掉,所以除非你有一个基于Erlang基本消息的机制,否则你将会这样做。从来不知道这没有发生。服务器也可能在收到消息之前死掉,或者即使在发送消息时它仍处于活动状态,也可以处理它,因此,如果您对消息丢失表示担心,解决方案的一个组件必须是可靠的消息机制。在您的情况下,合理且现成的解决方案是gen_server:call
和gen_fsm:sync_send_event
,而不仅仅是发送消息。
这消除了您希望实施的任何产生解决方案丢失消息的问题。也就是说,您将知道消息丢失或失败,然后您可以采取任何适当的行动。
现在,对于服务器的实际产生,总会出现竞争条件,无论您如何实现,多个进程可能会尝试生成相同的服务器(具有给定名称的服务器);在你做任何其他事情之前你要查找名字(例如erlang:whereis/1
)的任何东西都可能已经过时了(它可能会返回一个PID,但PID可能会在你发消息之前死掉,或者它可能会返回{{1}但是其他一些进程可以在你尝试之前注册这个名称)所以竞赛获胜(或失败)的唯一一点就是调用undefined
时。
你知道那可能会有一场比赛,但最多可能有一名获胜者。它可能不是你,其他一些过程可能会让你失望,但是因为你命名的过程并不重要,你可以简单地生成你的gen_server,给它起名称来自己注册,然后发送消息它的名字:
erlang:register/2
谁赢得了比赛并不重要(gen_server:start({local, Name}, ?MODULE, [], []),
gen_server:call(Name, Message)
来电可能会返回gen_server:start/4
)但是那么重要的是,有人应该赢了,所以到{error,{already_started, Pid}}
之后有一切成功的机会。
你显然需要确保调用返回了一个合适的成功结果,从技术上讲,你可以检查一个gen_server:call
例外并尝试再次产生它,但是你必须确保这个没有& #39; t成为一个无限循环。
说实话,虽然你不关心监督,但我仍然可能会对它进行监督。在这种情况下,noproc
主管的重启策略设置为simple_one_to_one
,因此它不会重新生成拟合。您的服务器将被收集在一个地方,而不仅仅是浮动,您将获得主管报告,这可能是一件坏事。遗憾的是,由于此处没有重新启动,您不会失控重启保护,因此您仍需要担心(除非您将temporary
更改为temporary
)。您的有效仲裁点将为transient
,您可以将所需的流程名称作为参数传递。
答案 1 :(得分:2)
正如迈克尔建议的那样,我也会去监督。
您可以使用{via,Module,ViaName}
中的gen_server:start_link
来使用atoms
以外的其他名称。有关详细信息,请参阅此处:http://erlang.org/doc/man/gen_server.html#start_link-4
例如,使用gproc
gen_server:start_link({via, gproc, {n, l, {?MODULE, Name}}, ?MODULE, [], []).
在调用gen_server时不要忘记使用相同的{via, gproc, ...}
结构,而不是仅仅使用Name
:
gen_server:call({via, gproc, {n, l, {?MODULE, Name}}, {execute_command, Command}).
我倾向于像这样定义via
:
-define(SERVER(Name), {via, gproc, {n, l, {?MODULE, Name}}}).
然后使用它:
gen_server:start_link(?SERVER("Testing"), ?MODULE, [], []).
gen_server:call(?SERVER("Testing"), {execute_command, Command}).
然后,您可以在主管中使用simple_one_for_one
策略和temporary
子规范启动它,如下所示:
<强>监强>
-module(my_cool_sup).
-behaviour(supervisor).
%% API
-export([start_link/1, start_child/1]).
%% Supervisor callbacks
-export([init/1]).
-define(SERVER, ?MODULE).
%% Helper macro for declaring children of supervisor
-define(CHILD(ChildName, Type, Args), {ChildName, {ChildName, start_link, Args}, temporary, 5000, Type, [ChildName]}).
%%====================================================================
%% API functions
%%====================================================================
start_link() ->
supervisor:start_link({local, ?SERVER}, ?MODULE, []).
start_child(Name) ->
supervisor:start_child(?SERVER, [Name]).
%%====================================================================
%% Supervisor callbacks
%%====================================================================
init([]) ->
RestartStrategy = {simple_one_for_one, 1, 5},
Children = [?CHILD(my_cool_server, worker, [])],
{ok, { RestartStrategy, Children} }.
<强> gen_server 强>
-module(my_cool_server).
-behavior(gen_server).
%% API
-export([start_link/3, execute_command/3]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-define(SERVER(Name), {via, gproc, {n, l, {?MODULE, Name}}}).
%%%===================================================================
%%% API
%%%===================================================================
start_link(Name) ->
gen_server:start_link(?SERVER(Name), ?MODULE, [], []).
execute_command(Name, Command) ->
gen_server:call(?SERVER(Name), {execute_command, Command}).
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
%% Your normal gen_server callbacks here...
现在,您可以使用my_cool_sup:start_child("My cool name").
启动子进程。他们将受到监督,如果他们已经开始,它将返回already_started
但不会抛出错误。
查看start_child
了解详情:http://erlang.org/doc/man/supervisor.html#start_child-2
答案 2 :(得分:0)
我解决此问题的首选方法是使用gen_tracker库。它通过内置的本地进程注册表提供符合OTP管理程序行为的通用管理程序。
它绝对不能涵盖所有可能的情况,但在非过于复杂的情况下使用也很好。