【问题标题】:Storing supervisor children PIDs存储主管子 PID
【发布时间】:2011-12-28 21:50:02
【问题描述】:

我是 Erlang 初学者,正在尝试实现我的第一个 Erlang 应用程序。它是一个网络监控工具,应该对指定的主机执行 ping 请求。实际上发送ICMP不是目的,我对应用程序结构更感兴趣。目前我有monitor_app、monitor_sup(root sup)、pinger_sup 和pinger(worker)。这是 pinger_sup:

-module(pinger_sup).
-behaviour(supervisor).
-export([start_link/0, start_child/1, stop_child/1]).
-export([init/1]).

start_link() ->
    supervisor:start_link({local, ?MODULE}, ?MODULE, []).

start_child(Ip) ->
    {ok, Pid} = supervisor:start_child(?MODULE, Ip),
    put(Pid, Ip),
    {ok, Pid}.

stop_child(Ip) ->
    Children = get_keys(Ip),
    lists:foreach(fun(Pid) ->
            pinger:stop(Pid)
        end,
        Children).

init(_Args) ->
    Pinger = {pinger, {pinger, start_link, []},
        transient, 2000, worker, [pinger]},
    Children = [Pinger],
    RestartStrategy = {simple_one_for_one, 4, 3600},
    {ok, {RestartStrategy, Children}}.

还有 pinger 本身:

-module(pinger).
-behaviour(gen_server).
-export([start_link/1, stop/1, stop_ping/1, ping/1, ping/2]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
-define(PING_INTERVAL, 5000).

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

stop(Pid) ->
    gen_server:cast(Pid, stop).

stop_ping(Ip) ->
    pinger_sup:stop_child(Ip).

ping(Ip) ->
    pinger_sup:start_child(Ip).

ping(_Ip, 0) ->
    ok;
ping(Ip, NProc) ->
    ping(Ip),
    ping(Ip, NProc - 1).

init(Ip) ->
    erlang:send_after(1000, self(), do_ping),
    {ok, Ip}.

handle_call(_Request, _From, State) ->
    {noreply, State}.

handle_cast(stop, State) ->
    io:format("~p is stopping~n", [State]),
    {stop, normal, State}.

handle_info(do_ping, State) ->
    io:format("pinging ~p~n", [State]),
    erlang:send_after(?PING_INTERVAL, self(), do_ping),
    {noreply, State};
handle_info(Info, State) ->
    io:format("Unknown message: ~p~n", [Info]),
    {noreply, State}.

terminate(_Reason, State) ->
    io:format("~p was terminated~n", [State]),
    ok.

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

它缺少 cmets,但我认为它对任何 Erlang 开发人员来说都非常简单和熟悉。我对这段代码有几个问题:

1) init 中的 *erlang:send_after* 是一个好的解决方案吗?如果我需要进程立即开始执行工作而不从外部触发他的功能,那么最佳做法是什么?

2) 目前我使用 pinger:ping("127.0.0.1"). 命令启动 ping。 pinger 模块要求 pinger_sup 启动子进程。该过程开始后,我想停止它。最好的方法是什么?我应该通过 sup 终止它还是应该向他发送停止命令?我应该如何存储进程PID?目前我使用进程字典,但在实现它之后,我意识到字典实际上不属于 sup (它是一个 shell 字典或任何其他 stop_child 调用者)。如果我使用 ETS 并且 sup 被终止,这些死进程 PID 会永远留在 ETS 中导致某种内存泄漏吗?

感谢任何答案和代码 cmets。

谢谢!

【问题讨论】:

    标签: erlang


    【解决方案1】:

    1) init 中的 erlang:send_after 是一个好的解决方案吗?

    没有。

    如果我需要进程立即开始执行工作而不从外部触发他的功能,那么最佳做法是什么?

    here。把timeout设为0,然后做:

    handle_info(timeout, State) ->
        whatever_you_wish_to_do_as soon_as_server_starts,
        {noreply, State}.
    

    零超时意味着服务器将在完成初始化后立即向自己发送“超时”信息,然后再处理任何其他调用/强制转换。

    另外,请参阅timer module。不要在 handle_info(do_ping, State) 中递归调用 send_after,只需启动计时器并告诉他每隔 ?PING_INTERVAL 向您发送“do_ping”。

    2) 目前我使用 pinger:ping("127.0.0.1") 启动 ping。命令。 pinger 模块要求 pinger_sup 启动子进程。该过程开始后,我想停止它。最好的方法是什么?我应该通过 sup 终止它还是应该向他发送停止命令?

    您应该向他发送停止命令。如果 gen_server 可以自己完成,为什么要使用主管杀死 gen_server? :-)

    我应该如何存储进程PID?目前我使用进程字典,但在实现它之后,我意识到字典实际上不属于 sup (它是一个 shell 字典或任何其他 stop_child 调用者)。如果我使用 ETS 并且 sup 被终止,这些死进程 PID 会永远留在 ETS 中导致某种内存泄漏吗?

    一旦所有者进程终止,ETS 表就会被破坏。所以,那里没有内存泄漏。

    但是,以您的方式存储 PID 不是“erlang 方式”。相反,我建议制作一个主管树。我建议 pinger_sup 启动一个处理给定 IP 的 supervisor ,而不是将所有 pinger 工作人员放在 pinger_sup 下然后记住哪个工作人员 ping 哪个 IP。然后这个主管启动所需数量的工人。

    现在,当您希望停止 ping 某个 IP 时,您只需杀死该 IP 的主管,他会自动杀死他的孩子。

    您如何知道哪个主管处理哪个 IP?好吧,将 IP 放在主管的名字中 :-) 当生成处理 IP 的主管时,请执行以下操作:

    -module(pinger_ip_sup).
    
    start_link(Ip) ->
        supervisor:start_link({global, {?MODULE, Ip}}, ?MODULE, []).
    

    然后,当你希望停止 ping 一个 Ip 时,你只需用名称 {global, {pinger_ip_sup, Ip}} 杀死主管,他就会杀死他的孩子 :-)

    编辑有关 cmets:

    如果您希望处理产生超时的错误,您可以保留一个状态变量,它会告诉您是由 init 产生的超时还是由错误产生的超时。例如:

    -record(pinger_state, {ip, initialized}).
    
    init(Ip) ->
        State = #pinger_state{ip = Ip, initialized = false}.
        {ok, State, 0}.
    
    handle_info(timeout, State#pinger_state{initialized = false}) ->
        whatever_you_wish_to_do_as soon_as_server_starts,
        New_state = State#pinger_state{initialized = true}.
        {noreply, New_state}.
    
    handle_info(timeout, State#pinger_state{initialized = true}) ->
        error_handling,
        {noreply, State}.
    

    这样您就可以使用此机制并处理超时错误。但是,这里真正的问题是:您是否预计会出现超时错误?

    至于计时器:是的,如果您计划进行 DDOS 攻击或类似的事情,计时器会产生一些开销 :-D 如果您不打算疯狂地创建和取消计时器,使用计时器模块会更加优雅.这是你必须做出的设计选择:你想要一个干净优雅的代码,还是在其他一切都崩溃时可以站立的代码。我怀疑你需要后者。但是,你最清楚。

    【讨论】:

    • 感谢您的回答,dijxtra,抱歉耽搁了。我对您的一些答案有疑问,并试图找到更多信息。您建议在 pinger init 函数中使用 timeout = 0 以使其在 handle_info 中启动超时消息。 handle_info 的超时不是为某些我确实需要将其作为超时处理的错误情况保留的吗?这种方法不会覆盖这种行为吗?
    • 那你说timer模块比erlang:send_after好。我发现了一个“Common Caveats”文档,它说使用 erlang:send_after 比使用计时器模块更有效:erlang.org/doc/efficiency_guide/commoncaveats.html。可能这里有一些我不明白的地方。你能澄清一下吗?您答案的其余部分是有道理的。虽然我有点意识到根据它的名字杀死一些东西。可能是 Erlang 的方式,在我看来,根据对象名称而不是它的状态做出决定是不寻常的。反正我会试试的:)
    • 我将您的 cmets 的答案添加到答案本身,因为它太长了,无法发表评论。
    • 一年后回到 Erlang,我发现这个话题更加有用和启发。我希望我可以投票两次。再次感谢:)
    • 注意:设置0 的超时时间并不意味着timeout 将是收到的第一条消息。如果您的 gen_server 是使用 gen_server:start_link/4 注册的,则注册发生在调用 Module:init/1 之前,因此其他进程可以在此期间看到它并发送消息。解决方案是使用gen_server:start_link/3,并且只有在准备好接收消息时才注册gen_server
    【解决方案2】:

    你需要的“存储主管子 PID”是 二郎:注册(原子,pid)

    【讨论】:

      猜你喜欢
      • 2023-03-13
      • 2013-10-07
      • 2017-03-15
      • 1970-01-01
      • 2014-11-30
      • 1970-01-01
      • 2018-10-09
      • 2012-11-11
      • 1970-01-01
      相关资源
      最近更新 更多