每次只能运行一个程序实例

最近我想在服务器上安排一个Perl应用程序每分钟运行一次,但如果该应用程序的实例已经在运行,它应该退出并什么也不做。这是一个常见问题,我通过锁文件解决了它。让我们看看如何在Perl中使用锁文件。

锁文件是什么?

大多数操作系统都支持文件锁定——它是防止多个进程同时更新文件并造成数据丢失的必要工具。当进程访问文件以防止其他进程更改它们时,它们会获得文件锁,并在完成时释放文件锁,使文件可以被其他进程再次使用。

程序可以使用锁文件原则来防止多个实例同时运行。当程序启动时,它会尝试锁定锁文件,如果成功,则执行程序,否则退出。当程序进程结束时,操作系统会移除它获得的任何锁。您可能之前见过锁文件,它们通常是带有 .lock 扩展名的普通文件。

Perl中的文件锁定

Perl为文件锁定提供了 flock 函数。它接受一个文件句柄和一个表示锁类型的常量值。因此,要获取一个文件的独占锁,我可以这样做

open my $file, ">", "app.lock" or die $!; 
flock $file, 2 or die "Unable to lock file $!";
# we have the lock

此代码首先打开一个写入文件句柄到文件 app.lock。如果成功,它通过调用带有数字 2 的 flock 尝试获取文件的独占锁。独占锁意味着在锁活动期间没有其他进程可以访问文件。记住锁类型的常量值可能很痛苦,所以如果请求的话, Fcntl 模块会导出常量名称。我将更新代码来做这件事

use Fcntl qw(:flock);
open my $file, ">", "app.lock" or die $!; 
flock $file, LOCK_EX or die "Unable to lock file $!";
# we have the lock

此代码与之前做的是同一件事,但我们不需要记住锁类型的常量值(LOCK_EX == 独占锁)。注意没有必要解锁文件——当程序退出时,锁将自动移除。

非阻塞 flock

到目前为止一切顺利,但我们有一个问题。如果文件被锁定, flock 将会阻塞并使我们的程序等待,直到锁被移除。我想要的程序立即退出,如果不能获得锁。然而,检查文件是否被锁定只能通过 flock 来实现!幸运的是, flock 开发者已经考虑了这个问题,我可以传递一个额外的参数来表示我想要一个非阻塞锁。

use Fcntl qw(:flock);
open my $file, ">", "app.lock" or die $!; 
flock $file, LOCK_EX|LOCK_NB or die "Unable to lock file $!";
# we have the lock

我在 flock 参数中添加了 |LOCK_NB,现在如果它不能获得独占锁,它将立即返回 false。这提供了我所需要的非阻塞行为。

测试它

我将把这个锁定代码放入一个快速的脚本中,这样我就可以测试锁功能。

#!/usr/bin/env perl
use Fcntl qw(:flock);
open my $file, ">", "app.lock" or die $!; 
flock $file, LOCK_EX|LOCK_NB or die "Unable to lock file: $!";

sleep(60);

我将把这个脚本保存为 sleep60.pl 并在终端测试它。

$ chmod 700 sleep60.pl
$ ./sleep60.pl&
[2] 21505
$ ./sleep60.pl
Unable to lock file Resource temporarily unavailable at ./sleep60.pl line 4.

看起来不错!我试着运行这个脚本两次,第二次时,系统打印了预期的错误信息并退出了。

避免使用外部文件

使用外部文件感觉有点脏。我真的想做的整理是,一旦程序完成,就删除锁文件。然而,解锁和删除文件需要额外的步骤,这可能会引入 竞争条件。那么,如果我们从未创建过它呢?一种方法是使用 __DATA__ 文件句柄,如下所示

#!/usr/bin/env perl
use Fcntl qw(:flock);
flock DATA, LOCK_EX|LOCK_NB or die "Unable to lock file $!";

sleep(60);
__DATA__

本版本的脚本打开了DATA文件句柄的锁,并且没有创建任何外部文件。Mark Jason Dominus多年前就展示了这个巧妙的技巧。[查看](http://perl.plover.com/yak/flock/samples/slide006.html)

#!/usr/bin/env perl
use Fcntl qw(:flock);
open our $file, '<', $0 or die $!;
flock $file, LOCK_EX|LOCK_NB or die "Unable to lock file $!";

sleep(60);

这释放了DATA,并且有一个额外的优点,即代码可以通过模块(使用our而不是my)导出。请注意,已将open参数更改为使用只读文件句柄,以避免截断程序的源代码!如果您需要这种行为,可以像上面所示自行实现,或者使用我的模块IPC::Lockfile,它将为您完成这项工作。如果您需要更精细的锁文件功能,请查看Sys::RunAlone,它使用相同的技巧(感谢BooK提供参考)。在CPAN上还有许多其他选项。

更新: 添加Sys::RunAlone参考 - 2015-11-28。


本文最初发布在PerlTricks.com

标签

David Farrell

David是一位职业程序员,他经常推文博客关于代码和编程艺术。

浏览他们的文章

反馈

这篇文章有问题吗?请通过在GitHub上打开问题或拉取请求来帮助我们。