29

在 Perl 中创建文件锁的最佳方法是什么?

最好是在文件上聚集还是创建一个锁定文件来放置锁定并检查锁定文件上的锁定?

4

14 回答 14

32

如果你最终使用了羊群,这里有一些代码可以做到:

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' - $!";

一些有用的链接:

针对您添加的问题,我会说要么在文件上加锁,要么在文件被锁定时创建一个称为“锁定”的文件,并在不再锁定时将其删除(然后确保您的程序服从那些语义)。

于 2008-08-29T18:18:40.487 回答
11

其他答案很好地涵盖了 Perl 群锁,但在许多 Unix/Linux 系统上实际上有两个独立的锁系统:BSDflock() 和基于 POSIX fcntl() 的锁。

除非您在构建 Perl 时提供特殊的配置选项,否则它的群将使用群(如果可用)。如果您只需要在应用程序中锁定(在单个系统上运行),这通常很好,并且可能是您想要的。但是,有时您需要与另一个使用 fcntl() 锁的应用程序交互(例如 Sendmail,在许多系统上),或者您可能需要跨 NFS 挂载的文件系统进行文件锁定。

在这些情况下,您可能需要查看File::FcntlLockFile::lockf。也可以在纯 Perl 中进行基于 fcntl() 的锁定(使用一些多毛且不可移植的 pack() 位)。

快速概览 flock/fcntl/lockf 的区别:

lockf 几乎总是在 fcntl 之上实现,只有文件级锁定。如果使用 fcntl 实现,则以下限制也适用于 lockf。

fcntl 通过 NFS 提供范围级锁定(在文件内)和网络锁定,但在 fork() 之后子进程不会继承锁定。在许多系统上,您必须让文件句柄以只读方式打开以请求共享锁,并以读写方式打开以请求独占锁。

flock 仅具有文件级锁定,锁定仅在单个机器内(您可以锁定 NFS 挂载的文件,但只有本地进程会看到锁定)。锁由子代继承(假设文件描述符未关闭)。

有时(SYSV 系统)flock 是使用 lockf 或 fcntl 模拟的;在某些 BSD 系统上,lockf 是使用flock 模拟的。一般来说,这类仿真效果不佳,建议您避免使用它们。

于 2011-10-08T08:02:49.490 回答
7

CPAN 救援:IO::LockedFile

于 2008-09-01T16:17:10.860 回答
6

瑞安 P 写道:

在这种情况下,文件实际上会在文件重新打开时被解锁一小段时间。

所以不要那样做。相反,open用于读/写的文件:

open my $fh, '+<', 'test.dat'
    or die "Couldn’t open test.dat: $!\n";

当您准备好编写计数器时,只需seek回到文件的开头即可。请注意,如果你这样做,你应该truncate在之前close,这样如果文件的新内容比以前的内容短,文件就不会留下尾随垃圾。(通常,文件中的当前位置在它的末尾,所以你可以写truncate $fh, tell $fh。)

另外,请注意我使用了三个参数open和一个词法文件句柄,并且我还检查了操作是否成功。请避免使用全局文件句柄(全局变量不好,mmkay?)和神奇的双参数open(这是 Perl 代码中许多(可利用的)错误的来源),并始终测试您open的 s 是否成功。

于 2008-09-17T08:10:03.157 回答
5

我认为用词法变量作为文件处理程序和错误处理来展示这一点会更好。使用 Fcntl 模块中的常量也比硬编码幻数 2 更好,这可能不是所有操作系统上的正确数字。

    使用 Fcntl ':flock'; # 导入 LOCK_* 常量

    # 打开文件进行追加
    open (my $fh, '>>', 'test.dat') 或死 $!;

    # 尝试以独占方式锁定文件,将等到您获得锁定
    羊群($fh,LOCK_EX);

    # 在这里对文件做一些事情(在我们的例子中打印到它)

    # 实际上你不应该解锁文件
    # 关闭文件将解锁
    close($fh) 或警告“无法关闭文件 $!”;

查看完整的羊群文档和 PerlMonks 上的文件锁定教程,尽管这也使用了旧样式的文件句柄用法。

实际上,我通常会跳过 close() 上的错误处理,因为如果它无论如何都失败了,我无能为力。

关于锁定什么,如果您正在处理单个文件,则锁定该文件。如果您需要一次锁定多个文件 - 为了避免死锁 - 最好选择一个您正在锁定的文件。如果这是您真正需要锁定的几个文件之一,或者您为锁定目的而创建的单独文件,这并不重要。

于 2008-09-17T07:34:27.940 回答
4

您是否考虑过使用LockFile::Simple 模块?它已经为您完成了大部分工作。

在我过去的经验中,我发现它非常易于使用且坚固耐用。

于 2008-09-17T02:49:02.603 回答
3
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' - $!";
于 2008-10-08T12:23:52.427 回答
1

我在这个问题中的目标是锁定一个用作多个脚本的数据存储的文件。最后,我使用了与以下类似的代码(来自 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

在他的示例中,我删除了flock 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);
于 2008-09-17T02:29:52.353 回答
1

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: $!";
}
于 2012-07-13T15:39:22.963 回答
1

锁定文件方法的一种替代方法是使用锁定套接字。有关此类实现,请参阅CPAN 上的Lock::Socket 。用法很简单,如下:

use Lock::Socket qw/lock_socket/;
my $lock = lock_socket(5197); # raises exception if lock already taken

使用套接字有几个优点:

  • 保证(通过操作系统)没有两个应用程序将持有相同的锁:没有竞争条件。
  • 保证(再次通过操作系统)在您的进程退出时整齐地清理,因此没有过时的锁需要处理。
  • 依赖于 Perl 运行的任何东西都很好支持的功能:例如,在 Win32 上的 flock(2) 支持没有问题。

明显的缺点当然是锁命名空间是全局的。如果另一个进程决定锁定您需要的端口,则可能会出现一种拒绝服务。

[披露:我是上述模块的作者]

于 2014-09-19T18:56:41.267 回答
0

使用羊群卢克。

编辑: 是一个很好的解释。

于 2008-08-29T18:16:35.827 回答
0

flock 创建 Unix 风格的文件锁,并且可以在大多数 OS 的 Perl 上运行。然而,flock 的锁只是建议性的。

编辑:强调羊群是便携式的

于 2008-08-29T18:16:47.743 回答
0

这是我在一个锁中读写的解决方案......

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$!";
于 2009-09-15T12:52:56.080 回答
0

Flock 可能是最好的,但需要您围绕它编写所有支持代码 - 超时、过时的锁、不存在的文件等。我确认 LockFile::Simple 但发现它开始将默认 umask 设置为只读而不是清理它。导致 modperl 上的多进程/多线程应用程序出现随机权限问题,我决定用一些空文件处理来包装 NFSLock。

于 2020-03-29T21:12:47.937 回答