Perl中的读写锁

4
我正在寻找一种在Perl中实现读写锁的好方法。这是为了同步不同Perl线程和/或进程在Windows和Unix上访问文件所需的。尝试过Fcntl :: flock,如果它按预期工作将完美地适用于我。不幸的是,在压力下,flock似乎允许在另一个线程中设置已锁定文件上的锁定。查看了一些CPAN模块,但大多数都是使用flock实现的。接下来,我计划评估Unix的fcntl和Windows的Win32 :: Mutex。这似乎是一个非常常见的任务,也许我错过了一些简单的解决方案。如果您知道任何解决方案,请指出来,谢谢!

嗯,flock 应该始终允许另一个线程获取锁,而不仅仅是在“紧张情况”下。 - ikegami
如果每个线程都有自己的文件句柄,这是真的吗? - AndyH
https://linux.die.net/man/2/flock 提供了有关在同一进程和相同或不同的文件描述符中 flock 可以成功的更多细节。 - ulix
有点不太清楚的是:flock()中描述符是独立处理的,而且“进程只能在文件上拥有一种类型的锁(共享或排他)。对已经锁定的文件进行后续的flock()调用将会将现有锁转换为新的锁模式”。 - AndyH
在同一个进程中,当另一个线程通过单独的文件描述符锁定文件时,是否应该阻止一个线程? - AndyH
哦,是的,flock 可以在由文件描述符指定的打开文件上工作:“在由_fd_指定的打开文件上应用或删除一个咨询锁。” - ulix
1个回答

13

flock不能在多线程中按照你的意愿工作。

你可以使用sysopen实现自己的锁定,当使用O_EXCL|O_CREAT时,如果文件存在会失败。

下面是一个例子,展示了子进程之间竞争锁的情况:

use warnings;
use strict;
use feature 'say';
use Fcntl;
use Time::HiRes qw(sleep);

my $lock_file = ".lock.$$";
sub get_lock {
    my ($file, $pid) = @_; 
    my $fh;
    while (not sysopen $fh, $file, O_WRONLY|O_EXCL|O_CREAT) {
        say "\t($$: lock-file exists ..)";
        sleep 0.5;
    }   
    say $fh $pid;
}
sub release_lock {
    my ($file, $pid) = @_; 
    unlink $file or die "Error unliking $file: $!";
    say "\t($$: released lock)";
}

my @pids;
for (1..4) {
    my $pid = fork // die "Can't fork: $!";
    if ($pid == 0) {
        sleep rand 1;
        get_lock($lock_file, $$);
        say "$$, locked and processing";
        sleep rand 1;
        release_lock($lock_file, $$);
        say "$$ completed.";
        exit
    }   
    push @pids, $pid;    
}
wait for @pids;

在处理锁文件名时最好使用File::Temp,但需要仔细阅读文档以了解细节。

使用3个进程的示例输出:

3659, 已锁定并正在处理
        (3660: 锁文件已存在..)
        (3658: 锁文件已存在..)
        (3659: 已释放锁)
3659 已完成。
3660, 已锁定并正在处理
        (3658: 锁文件已存在..)
        (3658: 锁文件已存在..)
        (3660: 已释放锁)
3660 已完成。
3658, 已锁定并正在处理
        (3658: 已释放锁)
3658 已完成。

O_EXCL可能在NFS下无法支持:您必须至少有2.6内核和NFSv3,否则将存在竞争条件。如果这是一个问题,则可以使用link(2)来获取锁。请参见man 2 open(还包括其他详细信息,因为sysopen使用open系统调用)。


要仅锁定文件访问权限,例如

sub open_with_lock {
    my ($file, $mode) = @_; 
    get_lock($lock_file, $$);
    open my $fh, $mode, $file or die "Can't open $file: $!";
    return $fh;
}

sub close_and_release {
    my ($fh) = @_; 
    close $fh;
    release_lock($lock_file, $$);
    return 1;
}

这些可以与get_lockrelease_lock一起放入模块中,锁文件名作为包全局变量。

一个简单的测试驱动程序

# use statements as above
use Path::Tiny;           # only to show the file

my $lock_file = ".lock.file.access.$$"; 
my $file = 't_LOCK.txt';    
my @pids;
for (1..4) 
{
    my $pid = fork // die "Can't fork: $!";

    if ($pid == 0) {
        sleep rand 1;
        my $fh = open_with_lock($file, '>>');
        say "$$ (#$_) opening $file ..";
        say $fh "this is $$ (#$_)";
        sleep rand 1;
        close_and_release($fh);
        say "$$ (#$_) closed $file.";
        say '---';
        exit;
    }   
    push @pids, $pid;
}
wait for @pids;

print path($file)->slurp;
unlink $file;

使用第一个示例中的use语句,通过3个fork,在运行过程中:

        (18956: "lock"-file exists ..)    # 打印顺序错乱
18954 (#1) 打开 t_LOCK.txt ...
        (18955: "lock"-file exists ..)
        (18956: "lock"-file exists ..)
        (18955: "lock"-file exists ..)
        (18954: 释放锁)
18954 (#1) 关闭 t_LOCK.txt.
---
18956 (#3) 打开 t_LOCK.txt ...
        (18955: "lock"-file exists ..)
        (18956: 释放锁)
18956 (#3) 关闭 t_LOCK.txt.
---
18955 (#2) 打开 t_LOCK.txt ...
        (18955: 释放锁)
18955 (#2) 关闭 t_LOCK.txt.
---
这是 18954 (#1)
这是 18956 (#3)
这是 18955 (#2)

(请注意,独立进程正在争夺STDOUT)


1
NFS也是"flock"存在的噩梦。 - mob
@sigh ... 我在这里使用 NFS 工作 :(. 当然,它也有好的一面(但对我来说并不经常发生)。 - zdim
@AndyH 添加了锁定文件访问的具体示例。 - zdim
@zdim 谢谢!您的解决方案没有提供独占/共享锁功能,但我仍然可以将其用作临时修复,然后基于您的代码创建读写锁。 - AndyH
@mpersico 如果你问为什么我没有将它作为一个模块在这里展示,那是为了让它更简单。如果你指的是CPAN...首先,感谢你的好评。一方面,我不确定它有多大的普遍用途(至少需要更多的工作来使其如此);另一方面,准备将其发布到那里需要付出很多努力。但如果这被认为是有用的,我会很高兴这样做! - zdim
后者。 :-) 这将节省很多人重复造轮子并发现它是正方形(包括我在内)。至于“通常有用”,做一个实验-模块化它,发布它,看看它有多少下载量(以及为你生成多少电子邮件)。 - mpersico

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接