2

我正在开发一个有 1 个主管和几个工人的应用程序。这些工作人员中的每一个都将打开一个 tcp 套接字,执行侦听,然后接受连接,在每个客户端到达时为它们生成一个进程(我不介意监督这些)。

我想在应用程序配置中配置监听地址,这样我就可以根据需要有尽可能多的地址来监听(至少需要 1 个地址,但可以指定任意数量)。每个地址都是一个 ip(或主机名)和一个端口地址。到目前为止,没有什么新鲜事。

我的问题是关于如何申报、启动和监督未知数量的工人。我的解决方案是(在运行时)在主管代码中动态生成 init/1 函数的结果,如下所示:

-define(
   CHILD(Name, Args),
   {Name, {
       ?MODULE, start_listen, Args
       }, permanent, 5000, worker, [?MODULE]
   }
).

init([]) ->
   {ok, Addresses} = application:get_env(listen),
   Children = lists:map(
       fun(Address) ->
           {X1,X2,X3} = os:timestamp(),
           ChildName = string:join([
               "Listener-",
               integer_to_list(X1),
               integer_to_list(X2),
               integer_to_list(X3)
           ], ""),
           ?CHILD(ChildName, [Address])
       end,
       Addresses
   ),
   {ok, { {one_for_one, 5, 10}, Children }}.

这允许我为每个工作人员动态生成一个名称,并生成工作人员定义。所以:

  1. 这个解决方案可以接受吗?在我看来,它并不那么优雅。这种用例是否有任何标准解决方案(或最佳实践等)?

  2. 我知道“simple_one_for_one”策略,它允许动态地将工作人员添加到主管。但是它也可以用来动态生成工人的名字吗?使用“simple_one_for_one”而不是我自己的使用“one_for_one”的解决方案会更好(以任何方式)吗?(再次,对于这种特殊情况)。

提前致谢,很抱歉发了这么长的帖子!:)

4

2 回答 2

5

simple_one_for_one

在您的工人的 init 函数中,您可以将他们注册到一个表中以将名称与他们的 PID 相关联,这样您就可以从名称中获取 pid。

您可以使用global模块(或gproc!)将名称与 pid 相关联。当一个进程死掉时,名字会被globalor自动删除gproc,所以当主管重启子进程时,名字就可用了。

您将在 supervisor:start_child/2 的参数列表(第二个参数)中传递名称

使用simple_one_for_one将允许您在主管初始化后动态添加更多侦听器。

如果你需要这个功能,simple_one_for_one是很好的解决方案。

如果您坚持在您的 sup 的 init 函数中使用动态内容的解决方案,您可以像这样清理代码,它可能看起来更优雅,也可能不看起来更优雅:

-define(
   CHILD(Name, Args),
   {Name, {
       ?MODULE, start_listen, Args
       }, permanent, 5000, worker, [?MODULE]
   }
).

generate_names(Adresses) -> generate_names(Adresses, 1, []).

generate_names([], _, Acc) -> Acc;
generate_names([Addr|Addresses], ID, Acc) -> generate_names(Addresses, ID+1, [{id_name(ID), Addr}|Acc]).

id_name(ID) -> "listener-" ++ integer_to_list(ID).

init([]]) ->
   {ok, Addresses} = application:get_env(listen),
   Children = [?CHILD(Name, Address) || {Name, Address} <- generate_names(Addresses)],
   {ok, { {one_for_one, 5, 10}, Children }}.

或者使用 alists:foldl而不是所有这些小函数来保持代码简短。

但无论如何,我会通过 init 的 Args 列表中的地址,而不是get_env在 init 内部调用以保持它的纯净。

于 2012-05-04T22:50:13.170 回答
0

从您的代码中,我知道您想从环境中获取孩子的数量。在rabbitmq开源项目worker_pool_sup.erl的源码中,我看过几乎相似的需求代码,代码优雅很多,我觉得对你有帮助。3个文件相关,worker_pool_sup.erl、worker_pool_worker.erl、work_pool.erl。

以下代码来自 worker_pool_sup.erl。

init([WCount]) ->
    {ok, {{one_for_one, 10, 10},
          [{worker_pool, {worker_pool, start_link, []}, transient,
            16#ffffffff, worker, [worker_pool]} |
           [{N, {worker_pool_worker, start_link, [N]}, transient, 16#ffffffff,
             worker, [worker_pool_worker]} || N <- lists:seq(1, WCount)]]}}.

关于如何使用,以下代码来自worker_pool.erl

get_worker_pid(WId) ->
    [{WId, Pid, _Type, _Modules} | _] =
        lists:dropwhile(fun ({Id, _Pid, _Type, _Modules})
                              when Id =:= WId -> false;
                            (_)               -> true
                        end,
                        supervisor:which_children(worker_pool_sup)),
    Pid.
于 2012-05-04T21:11:04.230 回答