如何在Perl中锁定文件?

如何在Perl中锁定文件?

How do I lock a file in Perl?

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

最好是对文件进行植绒还是创建一个锁定文件以放置一个锁并检查该锁定文件上的锁?


如果最终使用了flock,请执行以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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()文档

针对您所添加的问题,我想说是将锁定放置在文件上,或者创建一个被称为"'lock \\'的文件",只要文件被锁定,然后在不再锁定时将其删除(并且然后确保您的程序遵守这些语义。


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

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

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

flock / fcntl / lockf差异的快速概述:

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

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

flock仅具有文件级锁定,锁定仅在一台计算机内(您可以锁定NFS挂载的文件,但是只有本地进程才能看到该锁定)。子项继承锁(假设文件描述符未关闭)。

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


要救援的CPAN:IO :: LockedFile。


Ryan P写道:

In this case the file is actually unlocked for a short period of time while the file is reopened.

所以不要那样做。而是open用于读取/写入的文件:

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

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

此外,请注意,我使用了三个参数open和一个词法文件句柄,并且我还检查了操作是否成功。请避免使用全局文件句柄(全局变量不好,mmkay?)和不可思议的两个参数open(它是Perl代码中许多a(n可利用)bug的来源),并始终测试是否open成功。


我认为最好将词汇变量作为文件处理程序来显示
和错误处理。
使用Fcntl模块中的常量也比硬编码魔术数字2更好,因为魔术数字2可能不是在所有操作系统上都正确的数字。

1
2
3
4
5
6
7
8
9
10
11
12
13
    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上查看flock的完整文档和文件锁定教程,即使该文件也使用旧样式的文件句柄用法。

实际上,我通常跳过close()上的错误处理,因为没有
如果仍然失败,我可以做很多事情。

关于锁定内容,如果您使用的是单个文件,请锁定该文件。如果您需要一次锁定几个文件,那么-为了避免死锁-最好选择一个您要锁定的文件。不管这是您真正需要锁定的几个文件之一还是仅出于锁定目的而创建的单独文件,都没有关系。


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

根据我过去的经验,我发现它非常易于使用和坚固。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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' - $!";

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

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

使用套接字有两个优点:

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

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

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


基于http://metacpan.org/pod/File::FcntlLock

开发

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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\
"
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: $!";
}

我在这个问题上的目标是锁定一个文件,该文件用作多个脚本的数据存储。最后,我使用了与以下类似的代码(来自Chris):

1
2
3
4
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,因为close(FILE)也执行了此操作。真正的问题是,脚本启动时必须保留当前计数器,脚本结束时必须更新计数器。这是Perl遇到问题的地方,要读取文件:

1
2
 open (FILE, '<', test.dat');
 flock FILE, 2;

现在我想写出结果,并且由于我想覆盖文件,因此我需要重新打开并截断,结果如下:

1
2
 open (FILE, '>', test.dat'); #single arrow truncates double appends
 flock FILE, 2;

在这种情况下,实际上在重新打开文件时会在短时间内将文件解锁。这说明了外部锁定文件的情况。如果要更改文件的上下文,请使用锁定文件。修改后的代码:

1
2
3
4
5
6
7
8
9
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);

群可能是最好的,但需要您编写所有支持的代码-超时,过时的锁定,不存在的文件等。
我对LockFile :: Simple进行了修正,但是发现它开始将默认umask设置为只读,而不是将其清除。在modperl上的多进程/多线程应用程序上导致随机权限问题
我已经决定用一些空文件处理来packageNFSLock。


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
open (TST,"+< readwrite_test.txt") or die"Cannot open file\
$!"
;
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!\
"
;
$LINES[3]="Gekke henkie!\
"
;
# Write the file:
foreach $l (@LINES)
{
   print TST $l;
}
close(TST) or die"Cannot close file\
$!"
;

flock创建Unix风格的文件锁,在大多数OS的Perl上都可以使用。但是,flock的锁仅供参考。

编辑:强调羊群是可移植的


使用羊群卢克。

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


推荐阅读