gpt4 book ai didi

Erlang:如何处理长时间运行的 init 回调?

转载 作者:行者123 更新时间:2023-12-04 13:33:28 25 4
gpt4 key购买 nike

我有一个 gen_server当开始时尝试在监督树中的监督者下启动一定数量的子进程(通常是 10-20 个)。 gen_server 的 init 回调调用 supervisor:start_child/2对于每个需要的子进程。调用 supervisor:start_child/2是同步的,因此在子进程启动之前它不会返回。所有子进程也是 gen_servers,因此 start_link 调用在 init 回调返回之前不会返回。在 init 回调中,调用了第三方系统,这可能需要一段时间才能响应(当调用第三方系统 60 秒后超时时,我发现了这个问题)。与此同时,init 调用被阻塞,这意味着 supervisor:start_child/2也被屏蔽了。所以一直是调用 supervisor:start_child/2 的 gen_server 进程没有反应。在等待 start_child 函数返回时调用 gen_server 超时。因为这很容易持续 60 秒或更长时间。我想改变这一点,因为我的应用程序在等待时处于半启动状态。

解决此问题的最佳方法是什么?

我能想到的唯一解决方案是将与第三方系统交互的代码从 init 回调中移到 handle_cast 回调中。这将使 init 回调更快。缺点是我需要调用 gen_server:cast/2在所有子进程启动后。

有没有更好的方法来做到这一点?

最佳答案

我见过的一种方法是使用超时 init/1handle_info/2 .

init(Args) ->
{ok, {timeout_init, Args} = _State, 0 = _Timeout}.


...


handle_info( timeout, {timeout_init, Args}) ->
%% do your inicialization
{noreply, ActualServerState}; % this time no need for timeout

handle_info( ....

几乎所有结果都可以通过附加超时参数返回,这基本上是等待另一条消息的时间。它给定的时间过去了 handle_info/2被调用,用 timeout原子和服务器状态。在我们的例子中,超时等于 0,超时应该发生在 gen_server:start 之前。完成。意思是 handle_info甚至在我们能够将我们的服务器的 pid 返回给其他人之前就应该调用它。所以这个 timeout_init应该首先调用我们的服务器,并在处理其他任何事情之前给我们一些保证,我们完成初始化。

如果您不喜欢这种方法(不是真的可读),您可以尝试在 init/1 中向 self 发送消息
init(Args) ->
self() ! {finish_init, Args},
{ok, no_state_yet}.

...


handle_info({finish_init, Args} = _Message, no_state_yet) ->
%% finish whateva
{noreply, ActualServerState};

handle_info( ... % other clauses

同样,您要确保尽快将完成初始化的消息发送到此服务器,这对于在某个原子下注册的 gen_servers 非常重要。

编辑 经过对 OTP 源代码的一些更仔细的研究。

当您通过 pid 与服务器通信时,这种方法就足够了。主要是因为在你的 init/1之后返回了pid函数返回。但是在 gen_..的情况下有点不同开始于 start/4start_link/4我们自动以同名注册进程。您可能会遇到一种竞争条件,我想更详细地解释一下。

如果进程是注册一个通常会简化所有调用并转换到服务器,例如:
count() ->
gen_server:cast(?SERVER, count).

哪里 ?SERVER通常是模块名称(原子),它会正常工作,直到在此名称下是某个已注册(并且处于事件状态)的进程。当然,在引擎盖下这个 cast是标准的 Erlang 消息发送 ! .它没有什么神奇之处,与您在 init 中所做的几乎相同与 self() ! {finish ... .

但在我们的例子中,我们还要假设一件事。不仅仅是注册部分,还有我们的服务器完成了它的初始化。当然,由于我们正在处理消息框,因此需要多长时间并不重要,重要的是我们首先收到哪条消息。所以确切地说,我们希望收到 finish_init接收前的消息 count信息。

不幸的是,这种情况可能会发生。这是因为 gen OTP 中的 是 registered before init/1回调被调用。所以理论上,当一个进程调用 start将进入注册部分的功能,而不是其他人可以找到我们的服务器并发送 count消息,然后是 init/1函数将被调用 finish_init信息。机会很小( 非常非常小 ),但仍然可能发生。

对此有三种解决方案。

首先是什么都不做。在这种竞争条件的情况下, handle_cast会失败(由于函数子句,因为我们的状态是 not_state_yet 原子),并且主管会重新启动整个过程。

第二种情况是忽略这个坏消息/状态事件。这很容易实现
   ... ;
handle_cast( _, State) ->
{noreply, State}.

作为你的最后一个条款。不幸的是,大多数使用模板的人都使用这种不幸的(恕我直言)模式。

在这两个中你可能会失去一个 count信息。如果这确实是一个问题,您仍然可以尝试通过将最后一个子句更改为
   ... ;
handle_cast(Message, no_state_yet) ->
gen_server:cast( ?SERVER, Message),
{noreply, no_state_yet}.

但这还有其他明显的优势,我更喜欢“让它失败”的方法。

第三个选项是稍后注册过程。而不是使用 start/4并要求自动注册,使用 start/3 ,接收pid,自己注册。
start(Args) ->
{ok, Pid} = gen_server:start(?MODULE, Args, []),
register(?SERVER, Pid),
{ok, Pid}.

这样我们发送 finish_init在注册之前,以及在任何其他人可以发送和之前发送消息 count信息。

但是这种方法有其自身的缺点,主要是注册本身可能会以几种不同的方式失败。人们总是可以检查如何 OTP handles that ,并复制此代码。但这是另一个故事。

所以最终还是要看你需要什么,甚至在生产中会遇到什么问题。了解可能会发生什么不好的事情很重要,但我个人不会尝试解决任何问题,直到我真正遭受这种竞争条件的影响。

关于Erlang:如何处理长时间运行的 init 回调?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26809391/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com