题
在 Perl 中创建文件锁的最佳方法是什么?
最好是集中在文件上还是创建一个锁定文件来放置锁定并检查锁定文件上的锁定?
解决方案
如果您最终使用集群,请使用以下代码:
use Fcntl ':flock'; # Import LOCK_* constants
# We will use this file path in error messages and function calls.
# Don't type it out more than once in your code. Use a variable.
my $file = '/path/to/some/file';
# Open the file for appending. Note the file path is quoted
# in the error message. This helps debug situations where you
# have a stray space at the start or end of the path.
open(my $fh, '>>', $file) or die "Could not open '$file' - $!";
# Get exclusive lock (will block until it does)
flock($fh, LOCK_EX) or die "Could not lock '$file' - $!";
# Do something with the file here...
# Do NOT use flock() to unlock the file if you wrote to the
# file in the "do something" section above. This could create
# a race condition. The close() call below will unlock the
# file for you, but only after writing any buffered data.
# In a world of buffered i/o, some or all of your data may not
# be written until close() completes. Always, always, ALWAYS
# check the return value of close() if you wrote to the file!
close($fh) or die "Could not write '$file' - $!";
一些有用的链接:
- PerlMonks 文件锁定教程 (有点旧)
flock()
文档
为了回答您添加的问题,我想说要么在文件上加锁,要么创建一个文件,每当文件被锁定时您就将其称为“锁定”,并在文件不再锁定时将其删除(然后确保您的程序遵守那些语义)。
其他提示
其他答案很好地涵盖了 Perl 群锁定,但在许多 Unix/Linux 系统上实际上有两个独立的锁定系统:基于 BSD fancy() 和 POSIX fcntl() 的锁。
除非您在构建 Perl 时提供特殊的配置选项,否则它的集群将使用集群()(如果可用)。如果您只需要在应用程序中锁定(在单个系统上运行),这通常很好,并且可能是您想要的。然而,有时您需要与另一个使用 fcntl() 锁的应用程序(如许多系统上的 Sendmail)交互,或者您可能需要跨 NFS 安装的文件系统进行文件锁定。
在这些情况下,您可能需要查看 文件::FcntlLock 或者 文件::lockf. 。也可以在纯 Perl 中执行基于 fcntl() 的锁定(使用 pack() 的一些毛茸茸且不可移植的位)。
快速概述flock/fcntl/lockf差异:
lockf 几乎总是在 fcntl 之上实现,仅具有文件级锁定。如果使用 fcntl 实现,以下限制也适用于 lockf。
fcntl 通过 NFS 提供范围级锁定(在文件内)和网络锁定,但在 fork() 之后子进程不会继承锁。在许多系统上,必须以只读方式打开文件句柄才能请求共享锁,并以读写方式打开文件句柄才能请求独占锁。
集群仅具有文件级锁定,锁定仅在单个机器内(您可以锁定NFS挂载的文件,但只有本地进程才能看到锁定)。锁由子级继承(假设文件描述符未关闭)。
有时(SYSV系统)flock是使用lockf或fcntl来模拟的;在某些 BSD 系统上,lockf 是使用集群来模拟的。一般来说,这些类型的仿真效果很差,建议您避免使用它们。
CPAN 来救援: IO::锁定文件.
瑞安·P 写道:
在这种情况下,文件在重新打开时实际上会在短时间内解锁。
所以不要这样做。反而, open
读/写文件:
open my $fh, '+<', 'test.dat'
or die "Couldn’t open test.dat: $!\n";
当你准备好写计数器时,只需 seek
回到文件的开头。请注意,如果您这样做,您应该 truncate
就在之前 close
, ,这样,如果文件的新内容比以前的内容短,则文件不会留下尾随垃圾。(通常,文件中的当前位置位于其末尾,因此您可以编写 truncate $fh, tell $fh
.)
另外,请注意我使用了三个参数 open
和一个词法文件句柄,我还检查了操作是否成功。请避免全局文件句柄(全局变量不好,嗯?)和神奇的双参数 open
(这是 Perl 代码中许多可利用的错误的根源),并始终测试您是否 open
成功了。
我认为,用词汇变量作为文件处理程序和错误处理,将其显示为好得多。使用 Fcntl 模块中的常量也比硬编码幻数 2 更好,因为幻数 2 在所有操作系统上可能不是正确的数字。
use Fcntl ':flock'; # import LOCK_* constants # open the file for appending open (my $fh, '>>', 'test.dat') or die $!; # try to lock the file exclusively, will wait till you get the lock flock($fh, LOCK_EX); # do something with the file here (print to it in our case) # actually you should not unlock the file # close the file will unlock it close($fh) or warn "Could not close file $!";
查看完整内容 羊群记录 和 文件锁定教程 在 PerlMonks 上,尽管它也使用旧的文件句柄使用方式。
实际上,我通常会跳过Close()上的错误处理,因为无论如何失败,我无能为力。
关于锁定的内容,如果您正在处理单个文件,则锁定该文件。如果您需要一次锁定多个文件,那么为了避免死锁,最好选择您要锁定的一个文件。如果这是您真正需要锁定的几个文件之一,还是您只是为了锁定目的而创建的单独文件,这并不重要。
您是否考虑过使用 LockFile::简单模块?它已经为您完成了大部分工作。
根据我过去的经验,我发现它非常易于使用且坚固。
use strict;
use Fcntl ':flock'; # Import LOCK_* constants
# We will use this file path in error messages and function calls.
# Don't type it out more than once in your code. Use a variable.
my $file = '/path/to/some/file';
# Open the file for appending. Note the file path is in quoted
# in the error message. This helps debug situations where you
# have a stray space at the start or end of the path.
open(my $fh, '>>', $file) or die "Could not open '$file' - $!";
# Get exclusive lock (will block until it does)
flock($fh, LOCK_EX);
# Do something with the file here...
# Do NOT use flock() to unlock the file if you wrote to the
# file in the "do something" section above. This could create
# a race condition. The close() call below will unlock it
# for you, but only after writing any buffered data.
# In a world of buffered i/o, some or all of your data will not
# be written until close() completes. Always, always, ALWAYS
# check the return value on close()!
close($fh) or die "Could not write '$file' - $!";
我在这个问题中的目标是锁定用作多个脚本的数据存储的文件。最后我使用了与以下类似的代码(来自 Chris):
open (FILE, '>>', test.dat') ; # open the file
flock FILE, 2; # try to lock the file
# do something with the file here
close(FILE); # close the file
在他的示例中,我删除了集群 FILE, 8,因为 close(FILE) 也执行此操作。真正的问题是,当脚本启动时,它必须保存当前计数器,当脚本结束时,它必须更新计数器。这就是 Perl 有问题的地方,要读取文件,你:
open (FILE, '<', test.dat');
flock FILE, 2;
现在我想写出结果,因为我想覆盖文件,所以我需要重新打开并截断,结果如下:
open (FILE, '>', test.dat'); #single arrow truncates double appends
flock FILE, 2;
在这种情况下,文件在重新打开时实际上会在短时间内解锁。这演示了外部锁定文件的情况。如果要更改文件的上下文,请使用锁定文件。修改后的代码:
open (LOCK_FILE, '<', test.dat.lock') or die "Could not obtain lock";
flock LOCK_FILE, 2;
open (FILE, '<', test.dat') or die "Could not open file";
# read file
# ...
open (FILE, '>', test.dat') or die "Could not reopen file";
#write file
close (FILE);
close (LOCK_FILE);
开发自 http://metacpan.org/pod/File::FcntlLock
use Fcntl qw(:DEFAULT :flock :seek :Fcompat);
use File::FcntlLock;
sub acquire_lock {
my $fn = shift;
my $justPrint = shift || 0;
confess "Too many args" if defined shift;
confess "Not enough args" if !defined $justPrint;
my $rv = TRUE;
my $fh;
sysopen($fh, $fn, O_RDWR | O_CREAT) or LOGDIE "failed to open: $fn: $!";
$fh->autoflush(1);
ALWAYS "acquiring lock: $fn";
my $fs = new File::FcntlLock;
$fs->l_type( F_WRLCK );
$fs->l_whence( SEEK_SET );
$fs->l_start( 0 );
$fs->lock( $fh, F_SETLKW ) or LOGDIE "failed to get write lock: $fn:" . $fs->error;
my $num = <$fh> || 0;
return ($fh, $num);
}
sub release_lock {
my $fn = shift;
my $fh = shift;
my $num = shift;
my $justPrint = shift || 0;
seek($fh, 0, SEEK_SET) or LOGDIE "seek failed: $fn: $!";
print $fh "$num\n" or LOGDIE "write failed: $fn: $!";
truncate($fh, tell($fh)) or LOGDIE "truncate failed: $fn: $!";
my $fs = new File::FcntlLock;
$fs->l_type(F_UNLCK);
ALWAYS "releasing lock: $fn";
$fs->lock( $fh, F_SETLK ) or LOGDIE "unlock failed: $fn: " . $fs->error;
close($fh) or LOGDIE "close failed: $fn: $!";
}
锁的一种替代方案 文件 方法是使用锁 插座. 。看 锁::插座 CPAN 上的此类实现。用法如下简单:
use Lock::Socket qw/lock_socket/;
my $lock = lock_socket(5197); # raises exception if lock already taken
使用套接字有几个优点:
- (通过操作系统)保证没有两个应用程序会持有相同的锁:没有竞争条件。
- 保证(再次通过操作系统)在进程退出时干净利落地清理,因此没有陈旧的锁需要处理。
- 依赖于 Perl 运行的任何东西都很好支持的功能:例如,Win32 上的集群(2)支持没有问题。
当然,明显的缺点是锁命名空间是全局的。如果另一个进程决定锁定您需要的端口,则可能会出现拒绝服务的情况。
[披露:我是上述模块的作者]
集群创建Unix风格的文件锁,并且可以在大多数运行Perl的操作系统上使用。然而,羊群的锁只是建议性的。
编辑:强调flock是便携的
这是我的一锁读写解决方案......
open (TST,"+< readwrite_test.txt") or die "Cannot open file\n$!";
flock(TST, LOCK_EX);
# Read the file:
@LINES=<TST>;
# Wipe the file:
seek(TST, 0, 0); truncate(TST, 0);
# Do something with the contents here:
push @LINES,"grappig, he!\n";
$LINES[3]="Gekke henkie!\n";
# Write the file:
foreach $l (@LINES)
{
print TST $l;
}
close(TST) or die "Cannot close file\n$!";