【问题标题】:Running only one Perl script instance by croncron 只运行一个 Perl 脚本实例
【发布时间】:2011-01-15 00:39:54
【问题描述】:

我需要定期(约每 3-5 分钟)通过 cron 运行 Perl 脚本。我想确保一次只运行一个 Perl 脚本实例,因此下一个循环在前一个循环完成之前不会开始。可以/应该通过 cron、Perl 的某些内置功能来实现,还是我需要在脚本级别处理它?

我对 Perl 和 cron 还很陌生,因此非常感谢您的帮助和一般性建议。

【问题讨论】:

标签: perl cron


【解决方案1】:

我一直很幸运使用File::NFSLock 获得脚本本身的排他锁。

use Fcntl qw(LOCK_EX LOCK_NB);
use File::NFSLock;

# Try to get an exclusive lock on myself.
my $lock = File::NFSLock->new($0, LOCK_EX|LOCK_NB);
die "$0 is already running!\n" unless $lock;

这与其他锁文件建议有点相同,除了尝试获取锁之外我不需要做任何事情。

【讨论】:

  • 谢谢!我最喜欢它,因为它非常紧凑,并且不向文件写入任何内容。
  • 它仍在使用锁定文件。如果脚本是 foo.pl,那么它默认使用 foo.pl.NFSLock。所以你需要对目录有写权限。
  • 我喜欢这个解决方案,因为它很简单,只需要一个通用的 perl 模块。如果您使用它来防止 cron 作业重叠,您可能希望它退出而不大惊小怪,这样当您每分钟运行它时就不会收到无休止的 cron 电子邮件:-) 使用它而不是死:exit 0 unless $lock;
  • 要安装的是“apt-get install libfile-nfslock-perl”
  • 这个解决方案对我很有效,原因有二。 (1) 如果我用“LOCK_EX”而不是“LOCK_EX|LOCK_NB”调用File::NFSLlock->new(),那么锁请求变成阻塞并等待直到获得锁。这提供了基本的未排序的“穷人排队”。 (2) 即使持有锁的 perl 脚本突然终止(例如 kill -9),解决方案也不会被破坏。尽管锁定文件仍然存在,但它不再是 NFS 锁定的。下一次调用 File::NFSLlock->new() 将 NFS 锁定文件,然后在完成后正确删除文件。
【解决方案2】:

Sys::RunAlone 模块可以很好地满足您的需求。只需添加

  use Sys::RunAlone;

靠近代码顶部。

【讨论】:

  • 在致谢中阅读此内容后 +1:(感谢)Booking.com 在生产中大量使用它并允许我改进此模块。
  • 这是一个很棒的包。为我节省大量工作!
【解决方案3】:

使用File::Pid 将脚本的 pid 存储在文件中,脚本应在开始时检查该文件,如果找到则中止。您可以在脚本完成后删除 pidfile,但这并不是真正必要的,因为您可以稍后检查该进程 id 是否仍然存在(这也将解释脚本意外中止的情况):

use strict;
use warnings;
use File::Pid;

my $pidfile = File::Pid->new({file => /var/run/myscript});
exit if $pidfile->running();

$pidfile->write();

# ... rest of script...

# end of script
$pidfile->remove();
exit;

【讨论】:

  • 我惊讶地发现它的源代码中没有羊群调用!
  • @gbacon:随时提交补丁 :)
  • 我不会推荐这个答案。您有一个竞争条件(运行后调用 write)。这可能永远不会成为 cron 工作中的问题,但最好还是避免,因为有更好的答案(见接受的答案)。
【解决方案4】:

典型的做法是让每个进程打开和锁定某个文件。然后进程读取文件中包含的进程ID。

如果具有该 ID 的进程正在运行,则后来者安静地退出。否则,新的获胜者将其进程 ID(Perl 中的$$)写入 pidfile,关闭句柄(释放锁),然后继续其业务。

下面的示例实现:

#! /usr/bin/perl

use warnings;
use strict;

use Fcntl qw/ :DEFAULT :flock :seek /;

my $PIDFILE = "/tmp/my-program.pid";
sub take_lock {
  sysopen my $fh, $PIDFILE, O_RDWR | O_CREAT or die "$0: open $PIDFILE: $!";
  flock $fh => LOCK_EX                       or die "$0: flock $PIDFILE: $!";

  my $pid = <$fh>;
  if (defined $pid) {
    chomp $pid;
    if (kill 0 => $pid) {
      close $fh;
      exit 1;
    }
  }
  else {
    die "$0: readline $PIDFILE: $!" if $!;
  }

  sysseek  $fh, 0, SEEK_SET or die "$0: sysseek $PIDFILE: $!";
  truncate $fh, 0           or die "$0: truncate $PIDFILE: $!";
  print    $fh "$$\n"       or die "$0: print $PIDFILE: $!";
  close    $fh              or die "$0: close: $!";
}

take_lock;
print "$0: [$$] running...\n";
sleep 2;

【讨论】:

    【解决方案5】:

    我一直使用这个 - 小而简单 - 不依赖任何模块并且适用于 Windows + Linux。

    use Fcntl ':flock';                    
    
    ### Check to make sure there is only one instance ###
    open SELF, "< $0" or die("Cannot run two instances of this program");
    unless ( flock SELF, LOCK_EX | LOCK_NB ) {
        print "You cannot run two instances of this program , a process is still running";
        exit 1;
    }
    

    【讨论】:

      【解决方案6】:

      AFAIK perl 没有内置这样的东西。您可以在启动应用程序时轻松创建一个临时文件,并在脚本完成后将其删除。

      【讨论】:

      • 对不起,如果出现问题(机器重启)并且文件没有被删除,我预见会有很大的麻烦......
      • 公平地说,但是如果在您的脚本运行时重新启动机器,您可能无论如何都会遇到麻烦(除非脚本做了一些极其微不足道的事情)。
      • 脚本运行期间可能会发生重新启动。该脚本正在使用数据库事务,所以我想它会“幸存”重新启动以保持数据完整性。
      • 然后将 pid 写入该文件并检查具有该 pid 的进程是否仍在运行。
      【解决方案7】:

      考虑到我通常会编写一个守护进程(服务器)的频率,它会在作业运行之间很好地等待(即sleep()),而不是尝试使用 cron 进行相当细粒度的访问。

      如有必要,在 Unix / Linux 系统上,您可以从 /etc/inittab(或替换)运行它,以确保它始终运行,并在进程被杀死或终止时自动重新启动。

      添加:(并删除了一些不相关的内容)

      始终存在(运行,但大多是空闲的)守护程序方法的好处是消除了脚本的并发实例被 cron 自动启动的可能性。

      但这确实意味着您有责任正确管理时间,例如在存在重叠的情况下(即之前的运行仍在运行,而新的触发器发生)。这可以帮助您决定是使用分叉守护程序还是非分叉设计。线程在这种情况下没有任何优势,因此无需考虑它们的使用。

      这并不能完全消除多个进程运行的可能性,而是许多守护进程的常见问题。典型的解决方案是使用信号量,例如文件上的互斥锁,以防止运行第二个实例。进程结束时文件锁会自动忘记,因此在异常终止(例如电源故障)的情况下,锁本身不需要清理。

      使用 Fcntl 模块并使用带有 O_EXCL 标志(或 O_RDWR | O_CREAT | O_EXCL)的 Perl sysopen 的方法是 given by Greg Bacon。我要做的唯一区别是将排他锁定结合到 sysopen 调用中(即使用我建议的标志),然后删除多余的 flock 调用。哦,我会遵循 UNIX(和 Linux FHS)文件系统和 /var/run/daemonname.pid 的命名约定。

      另一种方法是使用 djb 的 daemontoolssimilar 来“守护”任务。

      【讨论】:

      • 运行一个守护进程并不能消除多个运行脚本的问题。某人或某事很容易意外地尝试启动第二个守护进程。您仍然需要一个 PID 文件来处理该问题。
      猜你喜欢
      • 1970-01-01
      • 2015-10-31
      • 1970-01-01
      • 1970-01-01
      • 2011-09-22
      • 1970-01-01
      • 2011-01-01
      • 2014-06-20
      • 1970-01-01
      相关资源
      最近更新 更多