【发布时间】:2011-08-28 12:27:53
【问题描述】:
在this 示例中,作者通过以下方式避免了死锁情况:
self() ! {start_worker_supervisor, Sup, MFA}
在他的 gen_server 的 init 函数中。我在我的一个项目中做了类似的事情,并被告知这种方法不受欢迎,最好立即超时。接受的模式是什么?
【问题讨论】:
标签: erlang erlang-otp
在this 示例中,作者通过以下方式避免了死锁情况:
self() ! {start_worker_supervisor, Sup, MFA}
在他的 gen_server 的 init 函数中。我在我的一个项目中做了类似的事情,并被告知这种方法不受欢迎,最好立即超时。接受的模式是什么?
【问题讨论】:
标签: erlang erlang-otp
这可能是非常有效和简单的解决方案,但我认为它不是好的 erlang 风格。 我正在使用 timer:apply_after,它更好,并且不会给人留下与外部模块/gen_* 交互的印象。
我认为最好的方法是使用状态机 (gen_fsm)。我们的大多数 gen_srvers 都是真正的状态机,但是因为设置 get_fsm 的初始工作,我认为我们最终得到了 gen_srv。
总而言之,我会使用 timer:apply_after 使代码清晰高效,或者使用 gen_fsm 成为纯粹的 Erlang 风格(甚至更快)。
我刚刚阅读了代码 sn-ps,但示例本身不知何故被破坏了——我不理解 gen_srv 操纵主管的这种构造。即使它是一些未来孩子池的经理,这也是明确执行此操作的更重要原因,而不依赖于进程的邮箱魔术。在一些更大的系统中调试这也是地狱。
【讨论】:
考虑使用新的gen_statem 行为。此行为支持生成 FSM 内部的事件:
状态函数可以使用 action() next_event 插入事件,并且这样的事件被插入作为下一个呈现给状态函数。也就是说,就好像它是最早的传入事件一样。专用的 event_type() 内部可用于此类事件,使它们不会被误认为是外部事件。
插入事件取代了调用自己的状态处理函数的技巧,例如,gen_fsm 以强制在其他事件之前处理插入的事件。
使用该模块中的action functionality,您可以确保您的事件在init 中生成并始终在任何外部事件之前处理,特别是通过在您的init 函数中创建next_event 操作。
例子:
...
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
【讨论】:
init/1 和你的handle_call/3 或handle_cast/2 调用它。我认为函数调用比向self() 发送消息更具可读性。
打电话给你自己的主管确实看起来是个坏主意,但我一直都在做类似的事情。
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),并且它没有如果我需要为其他事情使用超时,请碍事。
【讨论】:
gen_server 以gen_server:start_link/4 或gen_server:start/4 开头,则注册发生在调用Module:init/1 之前。任何在其注册名称下寻找gen_server 的人都可以找到它并向其发送比您自己的信息更早到达的消息。
只是为了补充已经说过的关于将服务器初始化分成两部分的内容,第一部分在init/1 函数中,第二部分在handle_cast/2 或handle_info/2 中。这样做实际上只有一个原因,那就是如果初始化预计需要很长时间。然后将其拆分将允许gen_server:start_link 更快地返回,这对于主管启动的服务器很重要,因为它们在启动子进程时“挂起”,而一个启动缓慢的子进程可能会延迟整个主管启动。
在这种情况下,我不认为拆分服务器初始化是不好的风格。
小心错误很重要。 init/1 中的错误将导致主管终止,而第二部分中的错误将导致主管尝试重新启动该子项。
我个人认为服务器向自己发送消息更好的样式,使用显式! 或gen_server:cast,就像使用良好的描述性消息一样,例如init_phase_2,这样会更容易看看发生了什么,而不是更匿名的超时。尤其是在其他地方也使用超时时。
【讨论】:
second_init 回调开始? :-)
坦率地说,我认为拆分初始化没有意义。在init 中进行繁重的工作确实会挂起主管,但使用timeout/handle_info,向self() 发送消息或将init_check 添加到每个处理程序(另一种可能性,虽然不是很方便)将有效地挂起调用进程。那么为什么我需要“工作”的主管和“不太工作”的 gen_server?干净的实现可能应该包括在初始化期间对任何消息的“not_ready”回复(为什么不从 init 产生完全初始化 + 完成后将消息发送回self(),这将重置“not_ready”状态),然后是“not_ready”回复应该由调用者正确处理,这增加了很多复杂性。只是暂停回复不是一个好主意。
【讨论】: