15

示例中,作者通过执行以下操作避免了死锁情况:

self() ! {start_worker_supervisor, Sup, MFA}

在他的 gen_server 的 init 函数中。我在我的一个项目中做了类似的事情,并被告知这种方法不受欢迎,最好立即超时。接受的模式是什么?

4

5 回答 5

14

Erlang 19+ 的更新

考虑使用新gen_statem行为。此行为支持生成 FSM 内部的事件:

状态函数可以使用 action() next_event 插入事件,并且这样的事件被插入作为下一个呈现给状态函数。也就是说,就好像它是最早的传入事件一样。专用的 event_type() internal 可用于此类事件,使它们不可能误认为是外部事件。

插入事件取代了调用自己的状态处理函数的技巧,例如,gen_fsm 强制在其他事件之前处理插入的事件。

使用该模块中的操作功能,您可以确保您的事件在任何外部事件中生成并始终在任何外部事件之前处理,特别是通过在您的函数中init创建操作。next_eventinit

例子:

...

callback_mode() -> state_functions.

init(_Args) ->
    {ok, my_state, #data{}, [{next_event, internal, do_the_thing}]}

my_state(internal, do_the_thing, Data) ->
    the_thing(),
    {keep_state, Data);
my_state({call, From}, Call, Data) ->
    ...

...

旧答案

在设计时,gen_server您通常可以选择在三种不同的状态下执行操作:

  • 启动时,在init/1
  • 运行时,在任何handle_*功能中
  • 停车时,在terminate/2

一个好的经验法则是在对事件(调用、转换、消息等)采取行动时在处理函数中执行一些事情。在 init 中执行的东西不应该等待事件,这就是句柄回调的用途。

因此,在这种特殊情况下,会生成一种“假”事件。我会说似乎gen_server总是想启动主管的启动。为什么不直接做呢init/1?是否真的需要能够在中间处理另一条消息(handle_info/2而不是这样做的效果)?那个窗口非常小(从开始gen_server到发送消息到之间的时间self())所以它根本不可能发生。

至于死锁,我真的建议不要在你的 init 函数中调用你自己的主管。那只是不好的做法。启动工作进程的一个好的设计模式是一个顶级主管,下面有一个经理和一个工作主管。经理通过调用工人主管来启动工人:

[top_sup]
  |    \
  |     \
  |      \
 man  [work_sup]
       /  |  \
      /   |   \
     /    |    \
    w1   ...   wN
于 2011-05-19T07:19:30.610 回答
7

只是为了补充已经说过的关于将服务器初始化分成两部分的内容,第一部分在init/1函数中,第二部分在handle_cast/2orhandle_info/2中。这样做实际上只有一个原因,那就是如果预计初始化需要很长时间。然后将其拆分将允许gen_server:start_link更快地返回,这对于主管启动的服务器可能很重要,因为它们在启动他们的孩子时“挂起”,而一个启动缓慢的孩子可能会延迟整个主管启动。

在这种情况下,我不认为拆分服务器初始化是不好的风格。

小心错误很重要。错误init/1将导致主管终止,而第二部分中的错误将导致主管尝试重新启动该子项。

我个人认为服务器向自己发送消息是更好的风格,无论是使用显式的!还是使用 的gen_server:cast,就像使用良好的描述性消息一样,例如init_phase_2,它会更容易看到正在发生的事情,而不是更匿名超时。特别是如果在其他地方也使用超时。

于 2011-05-19T14:40:05.017 回答
6

打电话给你自己的主管确实看起来是个坏主意,但我一直在做类似的事情。

init(...) ->
   gen_server:cast(self(), startup),
   {ok, ...}.

handle_cast(startup, State) ->
   slow_initialisation_task_reading_from_disk_fetching_data_from_network_etc(),
   {noreply, State}.

我认为这比使用 timeout 和 handle_info 更清楚,它几乎可以保证没有消息可以领先于启动消息(在我们发送该消息之前没有其他人拥有我们的 pid),并且它不会进入如果我需要为其他事情使用超时。

于 2011-05-19T10:01:02.267 回答
0

这可能是非常有效和简单的解决方案,但我认为这不是好的 erlang 风格。我正在使用 timer:apply_after,它更好,并且不会给人留下与外部模块/gen_* 交互的印象。

我认为最好的方法是使用状态机(gen_fsm)。我们的大多数 gen_srvers 都是真正的状态机,但是因为最初的工作是设置 get_fsm,我认为我们最终得到了 gen_srv。

总而言之,我会使用 timer:apply_after 来使代码清晰高效,或者使用 gen_fsm 来实现纯粹的 Erlang 风格(甚至更快)。

我刚刚阅读了代码片段,但示例本身不知何故被破坏了——我不理解 gen_srv 操纵主管的这种构造。即使它是一些未来孩子池的经理,这也是明确执行此操作的更重要原因,而不依赖于进程的邮箱魔术。在一些更大的系统中调试这也是地狱。

于 2011-05-19T02:02:06.790 回答
0

坦率地说,我认为拆分初始化没有意义。做繁重的工作init确实会挂起主管,但是使用timeout/handle_info,向每个处理程序发送消息self()或添加init_check到每个处理程序(另一种可能性,虽然不是很方便)将有效地挂起调用进程。那么为什么我需要“工作”的主管和“不太工作”的 gen_server?干净的实现可能应该包括在初始化期间对任何消息的“not_ready”回复(为什么不从 init 产生完全初始化 + 将消息发送回self()完成时,这将重置“not_ready”状态),但是“not_ready”回复应该是正确的由调用者处理,这增加了很多复杂性。只是暂停回复不是一个好主意。

于 2011-05-19T19:55:03.733 回答