gpt4 book ai didi

perl - 如果间隔计时器事件始终准备就绪,为什么 AnyEvent::child 回调不会运行?

转载 作者:行者123 更新时间:2023-12-04 16:47:25 26 4
gpt4 key购买 nike

更新 此问题可以使用 https://github.com/zbentley/AnyEvent-Impl-Perl-Improved/tree/io-starvation 中的修复程序解决

上下文:

我正在将 AnyEvent 与一些同步代码集成。同步代码需要安装一些观察者(在定时器、子进程和文件上),等待至少一个观察者完成,做一些同步/阻塞/遗留的事情,然后重复。

我正在使用基于纯 perl AnyEvent::Loop 的事件循环,这对我目前的目的来说已经足够了;我需要它做的大部分是信号/过程/定时器跟踪。

问题:

如果我有一个可以暂时阻塞事件循环的回调,子进程退出事件/回调永远不会触发。最简单的例子我可以让 watch 成为一个子进程并运行一个间隔计时器。间隔计时器在完成之前做了一些阻塞:

use AnyEvent;

# Start a timer that, every 0.5 seconds, sleeps for 1 second, then prints "timer":
my $w2 = AnyEvent->timer(
after => 0,
interval => 0.5,
cb => sub {
sleep 1; # Simulated blocking operation. If this is removed, everything works.
say "timer";
},
);

# Fork off a pid that waits for 1 second and then exits:
my $pid = fork();
if ( $pid == 0 ) {
sleep 1;
exit;
}

# Print "child" when the child process exits:
my $w1 = AnyEvent->child(
pid => $pid,
cb => sub {
say "child";
},
);

AnyEvent->condvar->recv;

此代码使子进程陷入僵局,并一遍又一遍地打印“timer”,“永远”(我运行了几分钟)。如果从计时器的回调中删除了 sleep 1 调用,代码将正常工作并且子进程观察器会按预期触发。

我希望子监视器最终运行(在子进程退出后的某个时刻,事件队列中的任何间隔事件运行、阻塞和完成),但它没有。

sleep 1 可以是任何阻塞操作。它可以用忙等待或任何其他花费足够长的时间来代替。它甚至不需要花一秒钟;它似乎只需要 a) 在子退出事件/SIGCHLD 传递期间运行,以及 b) 导致间隔始终根据挂钟运行。

问题:

为什么 AnyEvent 从来没有运行我的子进程观察器回调?

如何将子进程退出事件与可能阻塞很长时间以至于下一个间隔到期的间隔事件复用?

我尝试过的:

我的理论是,由于在事件循环之外花费的时间而变得“就绪”的计时器事件可以无限期地抢占 AnyEvent 内部某处的其他类型的就绪事件(如子进程观察器)。我已经尝试了一些事情:

  • 使用 AnyEvent::Strict 不会以任何方式出现任何错误或改变行为。
  • 部分解决方案:在任何时候删除间隔事件确实会触发子进程观察器(就好像在 AnyEvent 中完成了一些内部事件轮询/队列填充,只有在没有计时器事件时才会发生根据挂钟已经“准备好”)。缺点:在一般情况下这是行不通的,因为我必须知道我的子进程何时退出才能知道何时推迟我的间隔,这是同义反复。
  • 部分解决方案:与子进程观察器不同,其他间隔计时器似乎能够很好地相互复用,因此我可以在另一个间隔计时器中安装对 waitpid 的手动调用以检查收割子孙。缺点:可以人为地延迟等待子进程(我的用例涉及大量频繁的进程创建/销毁),任何 AnyEvent::child 观察器 安装并成功触发auto-reap the child 而不是告诉我的 interval/waitpid 计时器,需要编排,而且通常感觉我在滥用 AnyEvent。

最佳答案

间隔是每次定时器回调开始之间的时间,即不是回调结束和下一次回调开始之间的时间。您设置了一个间隔为 0.5 的计时器,计时器的 Action 是休眠一秒钟。这意味着一旦定时器被触发,它将立即一次又一次地被触发,因为定时器返回后间隔总是结束。

因此,根据事件循环的实现,可能会发生没有其他事件将被处理的情况,因为它一遍又一遍地忙于运行相同的计时器。我不知道您使用的是哪个底层事件循环(检查 $AnyEvent::MODEL),但是如果您查看 AnyEvent::Loop 的源代码(纯 Perl 实现的循环,即模型是 AnyEvent::Impl::Perl)你会发现以下代码:

   if (@timer && $timer[0][0] <= $MNOW) {
do {
my $timer = shift @timer;
$timer->[1] && $timer->[1]($timer);
} while @timer && $timer[0][0] <= $MNOW;

如您所见,只要有需要运行的定时器,它就会忙于执行定时器。通过设置间隔 (0.5) 和计时器的行为(休眠一秒),总会有一个需要执行的计时器。

如果您改为更改计时器,以便通过将间隔设置为大于阻塞时间(例如 2 秒而不是 0.5 秒)来为其他事件的处理留出实际空间,则一切正常:

...
interval => 2,
cb => sub {
sleep 1; # Simulated blocking operation. Sleep less than the interval!!
say "timer";


...
timer
child
timer
timer

关于perl - 如果间隔计时器事件始终准备就绪,为什么 AnyEvent::child 回调不会运行?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37769891/

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