【问题标题】:What else can i do 'sleep' when the sleep() can't work well with alarm?当 sleep() 不能很好地与警报一起工作时,我还能做什么“睡眠”?
【发布时间】:2011-07-28 08:04:38
【问题描述】:

有很多文件说“你应该避免使用带警报的睡眠,因为许多系统使用警报来实现睡眠”。实际上,我正在为这个问题而苦恼。 那么,当 sleep() 不能很好地处理警报时,有没有人可以帮助我“睡觉”?我已经尝试过 Time::HiRes 模块的 'usleep' 和 select() 函数。但它们也不起作用。

【问题讨论】:

  • 出于好奇,您在哪个系统上发现sleepalarm 不兼容?
  • @mob,来自man alarm 在我登录的 Linux 机器上:“sleep() 可以使用 SIGALRM 实现;混合调用 alarm() 和 sleep() 是个坏主意。”这适用于 C 库调用,但我相信 Perl 只是为 alarm()sleep() 包装 C 函数。
  • 您的问题没有描述您的症状。大多数回答者都推测您遇到问题是因为睡眠被警报打断了。但是,您的问题并没有这么说。
  • 我也想知道你在哪个系统上不兼容。 @Ven'Tatsu,您引用的文档是样板文件,不一定适用于您所在的系统。我的系统有相同的文档,但睡眠没有使用警报实现。

标签: perl sleep alarm


【解决方案1】:

鉴于您正被闹钟打扰,因此无法可靠地使用 sleep() 或 select(),我建议将 Time::HiRes::gettimeofday 与 select() 结合使用。

这是一些我没有测试过的代码。它应该能够抵抗被信号中断,并且会休眠所需的秒数加上最多 0.1 秒。如果您愿意消耗更多的 CPU 周期而无济于事,则可以提高分辨率:

...
alarm_resistant_sleep(5); # sleep for 5 seconds, no matter what
...

use Time::HiRes;

sub alarm_resistant_sleep {
  my $end = Time::HiRes::time() + shift();
  for (;;) {
    my $delta = $end - Time::HiRes::time();
    last if $delta <= 0;
    select(undef, undef, undef, $delta);
  }
}

【讨论】:

  • 是的,上面的代码可以正常工作,除了 Time::HiRes::Time() 应该是 Time::HiRes::time()。这是一个很好的解决方案。谢谢。
  • 修正了错字,使其更精确,并减少了唤醒的频率。它应该只在$!{EINTR} 为真时循环。
  • 这个例子不适合我。换句话说,睡眠被我在调用alarm_resistant_sleep(5)之前调用的警报中断。我想知道是不是因为我的系统没有通过警报实现睡眠。 @ikegami,看来您可以正常工作了。你用的是什么系统?
  • @benrifkah,此解决方案(在我编辑之前或之后)与 sleep 的实现方式无关。您可以将 select 替换为 sleep 并且您会得到相同的结果(尽管精度较低,因为 sleep 仅适用于秒)。此解决方案适用于问题是由信号处理程序引起的中断。当信号进入并且您有处理程序来处理它以便让代码有机会处理信号时,系统调用被中断并返回错误 EINTR。但这意味着您需要在处理完信号后恢复/重复中断的呼叫。
  • @ikegami,感谢您的详细说明。如果我对您的理解正确,则此代码不会阻止睡眠被中断,而是在中断后继续睡眠适当的时间。
【解决方案2】:

你可以试试AnyEvent:

use AnyEvent;

my $cv = AnyEvent->condvar;
my $wait_one_and_a_half_seconds = AnyEvent->timer(
    after => 1.5,
    cb => sub { $cv->send }
);     
# now wait till our time has come
$cv->recv;

【讨论】:

  • 太棒了!我会试试这个。非常感谢。
【解决方案3】:

你可以通过system在一个新进程上休眠:

system ( "sleep", 5 );

还是我误解了这个问题?

【讨论】:

  • 是的,这就是我现在的工作方式。但它似乎不能跨平台。有没有原生方式?
【解决方案4】:

使用时(来自MySQL forum

use Sys::SigAction qw( set_sig_handler );
eval {
  my $hsig = set_sig_handler( 'ALRM', sub { my $canceled = 1; die; }, { mask=>[ qw( INT ALRM ) ] ,safe => 0 } );
  alarm($timeout);
  ...
  alarm(0);
}

我注意到,任何后续对sleep($delay) 的调用都使用短于$timeout$delay 将终止脚本执行,并打印出“Alarm clock”。

我发现的解决方法是再次调用alarm(),但使用的值不可能很大(3600),然后立即取消该警报。

eval {
  alarm(3600);
  print " .... Meeeep ....";  # Some trace
  alarm(0); 
};

然后我就可以使用sleep(),不再受到干扰。

以下示例(实时代码 sn-p):

sub unmesswithsleep {
  eval {
    alarm(3600);
    &tracing (8, " .... Meeeep ....");
    alarm(0); 
  };
}


sub lockDBTables {
  return (0) unless ($isdbMySQLconnect);
  my $stm = qq {
    LOCK TABLES 
      myBIGtable WRITE
  };

  my $timeout = 60;          # This is the timer set to protect against deadlocks. Bail out then.
  eval { 
    my $h = set_sig_handler( 'ALRM', sub { my $canceled = 1; die; }, { mask=>[ qw( INT ALRM ) ] ,safe => 0 } ); 
    alarm($timeout); 
    my $res = $dbmyh->do($stm) + 0;
    alarm(0);  # Reset alarm
  };

  if ( $@ =~ m/Die/i ) {
    $isdbTabledlocked = 0;
    &tracerr (0, "FATAL: Lock on Tables has NOT been acquired within ${timeout}s. Lock is set to <$isdbTabledlocked>.");
    &unmesswithsleep();   # MUST be called each time alarm() is used
    return (0);
  } else { 
    $isdbTabledlocked = 1;
    &tracing (2, "  Good: Lock on Tables has been acquired in time. Lock is set to <$isdbTabledlocked>.");
    &unmesswithsleep();   # MUST be called each time alarm() is used
    return (1);
  }
  # Can use sleep() now.
}

【讨论】:

    【解决方案5】:

    试试

    print "Start\n";
    select undef, undef, undef, 1;
    print "End\n";                                                                  
    

    这将休眠 1 秒。

    【讨论】:

    • 正如我所提到的,我已经尝试过了。我试过选择 undef,undef,undef,100;但它不起作用。还是谢谢你。
    • 如果您的代码被信号中断,那么不幸的是,发生的情况取决于系统。
    【解决方案6】:

    听起来您的睡眠代码被一些设置警报的代码打断了。这是设计使然,因此您会看到预期的行为。换句话说,闹钟*应该总是打断睡眠电话。

    如果您正在寻找一种纯粹的 perl 睡眠方式而不会被警报打断,您可以通过安装自己的警报信号处理程序来做到这一点。这样,当您的代码收到警报时,它不会中断您的处理。

    但是,一个重要的警告是,这将延迟由其他代码设置的任何警报。其他代码将延迟收到警报;在您的代码完成后。这意味着,如果您想与其他人一起玩得很好,最好使用其他解决方案之一。

    这是一个例子:

    #!/usr/bin/perl
    
    use POSIX;
    use strict;
    use warnings;
    
    # set an alarm
    print "Setting alarm\n";
    alarm 1;
    
    my $old_alarm;
    my $snoozed;
    {
      # store the previous alarm handler (if any)
      $old_alarm = $SIG{ALRM};
    
      # override the alarm handler so that we don't
      # get interrupted
      local $SIG{ALRM} = sub {
        print "got alarm; snoozing\n";
    
        # record the fact that we caught an alarm so that
        # we can propagate it when we're done
        $snoozed++;
      };
    
      # sleep for a while.
      for (1 .. 3) {
        print "z" x $_ ,"\n";
        sleep 1;
      }
    }
    
    # replace the old sleep handler;
    $SIG{ALRM} = $old_alarm
     if $old_alarm;
    
    # if we had to snooze fire an immediate alarm;
    if ($snoozed) {
      POSIX::raise(POSIX::SIGALRM);
    }
    

    您参考的文档暗示但没有描述不同的症状。通过闹钟实现睡眠时,您需要担心的主要事情是当有人调用睡眠时重置您的闹钟。

    *显然有些版本的 perl(例如:旧 Win32)警报不会中断睡眠。

    【讨论】:

    • 既然你提到了 Windows,sleep 是唯一一个 alarm 仿真会在 Windows 上中断的东西。
    • @ikegami,谢谢。我自己无法验证。我的 Win32 评论是关于警报中断睡眠的免责声明,基于我在此 OLD perlmonks 帖子 perlmonks.org/?node_id=300273 及其父线程中发现的内容。
    猜你喜欢
    • 2021-04-12
    • 1970-01-01
    • 2021-02-26
    • 1970-01-01
    • 2016-05-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多