在init中向self()发送消息是不是很糟糕?

时间:2011-05-19 01:50:55

标签: erlang otp

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

self() ! {start_worker_supervisor, Sup, MFA}

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

5 个答案:

答案 0 :(得分:14)

Erlang 19 +

的更新

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

  

state函数可以使用action()next_event插入事件,并且这样的事件作为next存在插入到state函数中。也就是说,好像它是最老的传入事件。专门的event_type()内部可用于此类事件,使其无法误认为是外部事件。

     

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

使用该模块中的action functionality,您可以确保在init生成事件,并始终在任何外部事件之前处理,特别是在{{1}中创建next_event操作功能。

示例:

init

旧答案

在设计... 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_*

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

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

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

self()

答案 1 :(得分:7)

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

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

小心错误很重要。 init/1中的错误将导致主管在第二部分发生错误时终止,因为它们会导致主管尝试重新启动该子项。

我个人认为服务器向自己发送消息的方式更好,可以使用明确的!gen_server:cast,也可以使用良好的描述性消息,例如{{1} ,更容易看到发生了什么,而不是更匿名的超时。特别是如果在其他地方也使用超时。

答案 2 :(得分: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),而且它没有如果我需要将超时用于其他事情,那就会妨碍你。

答案 3 :(得分:0)

这可能是非常有效和简单的解决方案,但我认为它不是好的erlang风格。 我正在使用计时器:apply_after,这是更好的,并没有给与外部模块/ gen _ *交互的印象。

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

总而言之,我会使用timer:apply_after使代码清晰有效或gen_fsm成为纯Erlang风格(甚至更快)。

我刚刚阅读了代码片段,但是示例本身在某种程度上被打破了 - 我不理解gen_srv操作管理器的这种构造。即使它是未来一些孩子的经理,这也是明确做到这一点的更重要的原因,而不依赖于进程的邮箱魔术。调试这个在一些更大的系统中也是地狱。

答案 4 :(得分:0)

坦率地说,我没有看到拆分初始化的重点。在init中执行繁重操作会导致主管挂起,但使用timeout/handle_info,向self()发送消息或向每个处理程序添加init_check(另一种可能性,但不是很方便)将有效挂起呼叫过程。那么为什么我需要“工作”主管“不太正常”gen_server?清理实现应该包括初始化期间任何消息的“not_ready”回复(为什么不在完成时从init +发送完全初始化消息发送回self(),这将重置“not_ready”状态),但是“未准备就绪” “回复应该由调用者正确处理,这增加了很多复杂性。暂停回复不是一个好主意。