Perl中的读写锁

时间:2017-10-18 19:48:35

标签: multithreading perl

我正在寻找一种在Perl中实现读/写锁的好方法。 这需要在Windows和Unix上同步来自不同Perl线程和/或进程的文件访问。 尝试过Fcntl :: flock,如果按预期工作,对我来说是完美的。不幸的是,看起来在压力下flock允许在另一个线程中锁定已经锁定的文件。 查看了一些CPAN模块,但大多数都是用flock实现的。 接下来我打算评估Unix的win和Win32 :: Mutex的fcntl。 这似乎是一项非常常见的任务,也许我错过了一些简单的解决方案。 如果您知道任何问题,请指点一下吗?

谢谢!

1 个答案:

答案 0 :(得分:9)

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, locked and processing
        (3660: lock-file exists ..)
        (3658: lock-file exists ..)
        (3659: released lock)
3659 completed.
3660, locked and processing
        (3658: lock-file exists ..)
        (3658: lock-file exists ..)
        (3660: released lock)
3660 completed.
3658, locked and processing
        (3658: released lock)
3658 completed.

在NFS下可能不支持O_EXCL:您必须至少拥有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个forks,运行

        (18956: "lock"-file exists ..)    # print out of order
18954 (#1) opening t_LOCK.txt ...
        (18955: "lock"-file exists ..)
        (18956: "lock"-file exists ..)
        (18955: "lock"-file exists ..)
        (18954: released lock)
18954 (#1) closed t_LOCK.txt.
---
18956 (#3) opening t_LOCK.txt ...
        (18955: "lock"-file exists ..)
        (18956: released lock)
18956 (#3) closed t_LOCK.txt.
---
18955 (#2) opening t_LOCK.txt ...
        (18955: released lock)
18955 (#2) closed t_LOCK.txt.
---
this is 18954 (#1)
this is 18956 (#3)
this is 18955 (#2)

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