如何在没有竞争条件的Erlang中按需启动gen_server或gen_fsm?

时间:2016-02-07 20:50:34

标签: multithreading erlang multiprocessing race-condition otp

我需要根据需要生成相同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的消息都会出现到中间过程并迷路。

我觉得我在这里缺少一些简单的东西。这不是一个不常见的模式,所以应该有一个很好的方法来做到这一点。我错过了什么?

3 个答案:

答案 0 :(得分:3)

出于您的目的,我不认为gproc:reg_or_locate/3确实为您提供了有用的东西。如果它返回一个PID(由于产生一个新进程,或者找到一个现有的进程),那么在你向它发送一条消息之前,进程仍然会死掉,所以除非你有一个基于Erlang基本消息的机制,否则你将会这样做。从来不知道这没有发生。服务器也可能在收到消息之前死掉,或者即使在发送消息时它仍处于活动状态,也可以处理它,因此,如果您对消息丢失表示担心,解决方案的一个组件必须是可靠的消息机制。在您的情况下,合理且现成的解决方案是gen_server:callgen_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管理程序行为的通用管理程序。

它绝对不能涵盖所有可能的情况,但在非过于复杂的情况下使用也很好。