在 init 中向 self() 发送消息是否不好?
在这个示例中,作者通过执行以下操作来避免死锁情况:
自己() ! {start_worker_supervisor, Sup, MFA}
在他的 gen_server 的 init 函数中。我在一个项目中做了类似的事情,并被告知这种方法不受欢迎,最好立即超时。可接受的模式是什么?
In this example, the author avoids a deadlock situation by doing:
self() ! {start_worker_supervisor, Sup, MFA}
in his gen_server's init function. I did something similar in one of my projects and was told this method was frowned upon, and that it was better to cause an immediate timeout instead. What is the accepted pattern?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
Erlang 19+ 的更新
考虑使用新的
gen_statem
行为。此行为支持生成 FSM 内部事件:使用该模块中的操作功能,您可以确保生成事件在
init
中,并且始终在任何外部事件之前进行处理,特别是通过在init
函数中创建next_event
操作。示例:
旧答案
在设计
gen_server
时,您通常可以选择在三种不同的状态下执行操作:init/1
>handle_*
函数terminate/2
中一个好的经验法则是在对事件(调用、强制转换、消息等)进行操作时执行处理函数中的内容。在 init 中执行的内容不应该等待事件,这就是句柄回调的用途。
因此,在这种特殊情况下,会生成一种“假”事件。我想说,
gen_server
似乎总是想要启动主管的启动。为什么不直接在init/1
中执行呢?是否确实需要能够处理中间的另一条消息(在handle_info/2
中执行此操作的效果)?该窗口非常小(gen_server
启动和向self()
发送消息之间的时间),因此它根本不可能发生。至于死锁,我真的建议不要在 init 函数中调用自己的主管。这只是不好的做法。启动工作进程的一个好的设计模式是一个顶级主管,下面有一个经理和一个工作主管。经理通过调用工人主管来启动工人:
Update for Erlang 19+
Consider using the new
gen_statem
behaviour. This behaviour supports generating of events internal to the FSM:Using the action functionality in that module, you can ensure your event is generated in
init
and always handled before any external events, specifically by creating anext_event
action in yourinit
function.Example:
Old answer
When designing a
gen_server
you generally have the choice to perform actions in three different states:init/1
handle_*
functionterminate/2
A good rule of thumb is to execute things in the handling functions when acting upon an event (call, cast, message etc). The stuff that gets executed in init should not wait for events, that's what the handle callbacks are for.
So, in this particular case, a kind of "fake" event is generated. I'd say it seems that the
gen_server
always wants to initiate the starting of the supervisor. Why not just do it directly ininit/1
? Is there really a requirement to be able to handle another message in-between (the effect of doing it inhandle_info/2
instead)? That windown is so incredibly small (the time between start of thegen_server
and the sending of the message toself()
) so it's highly unlikely to happen at all.As for the deadlock, I would really advise against calling your own supervisor in your init function. That's just bad practice. A good design pattern for starting worker process would be one top level supervisor, with a manager and a worker supervisor beneath. The manager starts workers by calling the worker supervisor:
只是为了补充已经说过的将服务器初始化分为两部分的内容,第一部分在
init/1
函数中,第二部分在handle_cast/2
或中>handle_info/2
。这样做实际上只有一个原因,那就是初始化预计需要很长时间。然后将其拆分将使 gen_server:start_link 更快地返回,这对于由主管启动的服务器非常重要,因为它们在启动其子级时“挂起”,而一个缓慢启动的子级可能会延迟整个主管的启动。在这种情况下,我不认为分割服务器初始化是不好的风格。
小心错误很重要。 init/1 中的错误将导致主管终止,而第二部分中的错误将导致主管尝试重新启动该子进程。
我个人认为服务器向自身发送消息是更好的风格,可以使用显式的
!
或gen_server:cast
,就像良好的描述性消息一样,对于例如init_phase_2
,它会更容易看到发生了什么,而不是更匿名的超时。特别是如果其他地方也使用了超时。Just to complement what has already been said about splitting a servers initialisation into two parts, the first in the
init/1
function and the second in eitherhandle_cast/2
orhandle_info/2
. There is really only one reason to do this and that is if the initialisation is expected to take a long time. Then splitting it up will allow thegen_server:start_link
to return faster which can be important for servers started by supervisors as they "hang" while starting their children and one slow starting child can delay the whole supervisor startup.In this case I don't think it is bad style to split the server initialisation.
It is important to be careful with errors. An error in
init/1
will cause the supervisor to terminate while an error in the second part as they will cause the supervisor to try and restart that child.I personally think it is better style for the server to send a message to itself, either with an explicit
!
or agen_server:cast
, as with a good descriptive message, for exampleinit_phase_2
, it will be easier to see what is going on, rather than a more anonymous timeout. Especially if timeouts are used elsewhere as well.给自己的主管打电话确实看起来是个坏主意,但我一直在做类似的事情。
我认为这比使用超时和handle_info更清晰,它几乎可以保证没有消息可以领先于启动消息(在我们发送该消息之前没有其他人拥有我们的pid),并且它不会进入启动消息如果我需要使用超时来做其他事情的话。
Calling your own supervisor sure does seem like a bad idea, but I do something similar all the time.
I think this is clearer than using timeout and handle_info, it's pretty much guaranteed that no message can get ahead of the startup message (no one else has our pid until after we've sent that message), and it doesn't get in the way if I need to use timeouts for something else.
这可能是非常高效且简单的解决方案,但我认为这不是好的 erlang 风格。
我正在使用timer:apply_after,它更好,并且不会给人留下与外部模块/gen_*交互的印象。
我认为最好的方法是使用状态机(gen_fsm)。我们的大多数 gen_srvs 实际上都是状态机,但是由于设置 get_fsm 的初始工作努力,我认为我们最终得到了 gen_srv。
总而言之,我会使用timer:apply_after使代码清晰高效,或者使用gen_fsm成为纯粹的Erlang风格(甚至更快)。
我刚刚阅读了代码片段,但示例本身在某种程度上被破坏了——我不理解 gen_srv 操纵主管的这种构造。即使它是未来子进程池的管理者,这也是显式执行此操作的更重要原因,而无需依赖进程的邮箱魔法。在一些更大的系统中调试这也将是地狱。
This may be very efficient and simple solution, but I think it is not good erlang style.
I am using timer:apply_after, which is better and does not make impression of interacting with external module/gen_*.
I think that the best way would be to use state machines (gen_fsm). Most of our gen_srvers are really state machine, however because initial work effort to set up get_fsm I think we end up with gen_srv.
To conclude, I would use timer:apply_after to make code clear and efficient or gen_fsm to be pure Erlang style (even faster).
I have just read code snippets, but example itself is somehow broken -- I do not understand this construct of gen_srv manipulating supervisor. Even if it is manager of some pool of future children, this is even more important reason to do it explicitly, without counting on processes' mailbox magic. Debugging this would be also hell in some bigger system.
坦率地说,我不认为分割初始化有什么意义。在
init
中执行繁重的工作确实会挂起主管,但使用timeout/handle_info
、向self()
发送消息或添加init_check
code> 到每个处理程序(另一种可能性,虽然不是很方便)将有效地挂起调用进程。那么为什么我需要“工作”主管和“不太工作”的 gen_server 呢?干净的实现可能应该包括在初始化期间对任何消息的“not_ready”回复(为什么不从 init 生成完整的初始化+在完成时将消息发送回self()
,这将重置“not_ready”状态),但是“未准备好”回复应该由调用者正确处理,这会增加很多复杂性。仅仅暂停回复并不是一个好主意。Frankly, I don't see a point in splitting initialization. Doing heavy lifting in
init
does hang supervisor, but usingtimeout/handle_info
, sending message toself()
or addinginit_check
to every handler (another possibility, not very convenient though) will effectively hang calling processes. So why do I need "working" supervisor with "not quite working" gen_server? Clean implementation should probably include "not_ready" reply for any message during initialization (why not to spawn full initialization from init + send message back toself()
when complete, which would reset "not_ready" status), but then "not ready" reply should be properly processed by the caller and this adds a lot of complexity. Just suspending a reply is not a good idea.