perl - 使用 Perl 并发追加到同一个文件

标签 perl concurrency locking

我需要升级用户必须完成 3 个步骤的 Perl CGI 脚本。在他们完成每个步骤后,脚本会记录用户完成的步骤。记录这一点很重要,因此我们可以向用户证明他们只完成了第一步,并没有完成所有三个步骤。

现在,该脚本正在为 CGI 脚本的每个实例创建 1 个日志文件。因此,如果 UserA 执行第 1 步,然后 UserB 执行第 1 步,然后执行第 2 步,然后第 3 步 - 然后 UserA 完成第 2 步和第 3 步,则日志文件的顺序将是。

LogFile.UserA.Step1
LogFile.UserB.Step1
LogFile.UserB.Step2
LogFile.UserB.Step3
LogFile.UserA.Step2
LogFile.UserA.Step3

日志文件以当前时间戳、随机数和进程 PID 命名。

这可以很好地防止同一个文件被多次写入,但目录很快就会获得数千个文件(每个文件只包含几个字节)。有一个过程可以轮换和压缩这些日志,但我不得不这样做,因此脚本每天只记录一个文件,以减少创建的日志文件的数量。

基本上,日志文件将在文件名中包含当前日期,并且无论何时 CGI 脚本需要写入日志,它都会附加到当天的一个日志文件中,而不管用户或他们所处的步骤。

不需要读取日志文件 - 唯一会发生的事情是 CGI 脚本的追加。日志轮换将在 7 天或更早的日志文件上运行。

我的问题是,处理并发追加到此日志文件的最佳方法是什么?我需要在追加之前锁定它吗?我找到了this page在 Perl Monks 上似乎表明“当多个进程正在写入同一个文件时,并且所有进程都打开了文件以进行附加,数据不应被覆盖。”

我了解到,仅仅因为可以做到并不意味着我应该这样做,但在这种情况下,最安全、最佳实践的方法是什么?

概括:
  • 并发追加到同一个文件
  • 每个追加到文件中只有一行,少于 50 个字符
  • 订单无所谓

  • 谢谢!

    最佳答案

    是的,使用 flock .

    下面是一个示例程序,从典型的前端内容开始:

    #! /usr/bin/perl
    
    use warnings;
    use strict;
    
    use Fcntl qw/ :flock /;
    

    然后我们指定日志的路径和将运行的客户端数量:
    my $log = "/tmp/my.log";
    my $clients = 10;
    

    要记录消息,请以附加模式打开文件,以便所有写入都自动结束。然后调用flock等待轮到我们拥有对日志的独占访问权限。起床后,写下消息和 close Handlebars ,它会自动释放锁。
    sub log_step {
      my($msg) = @_;
    
      open my $fh, ">>", $log or die  "$0 [$$]: open: $!";
      flock $fh, LOCK_EX      or die  "$0 [$$]: flock: $!";
      print $fh "$msg\n"      or die  "$0 [$$]: write: $!";
      close $fh               or warn "$0 [$$]: close: $!";
    }
    

    现在fork关闭 $clients子进程以随机间隔完成所有三个步骤:
    my %kids;
    my $id = "A";
    for (1 .. $clients) {
      my $pid = fork;
      die "$0: fork: $!" unless defined $pid;
    
      if ($pid) {
        ++$kids{$pid};
        print "$0: forked $pid\n";
      }
      else {
        my $user = "User" . $id;
        log_step "$user: Step 1";
        sleep rand 3;
        log_step "$user: Step 2";
        sleep rand 3;
        log_step "$user: Step 3";
        exit 0;
      }
    
      ++$id;
    }
    

    不要忘记等待所有 child 退出:
    print "$0: reaping children...\n";
    while (keys %kids) {
      my $pid = waitpid -1, 0;
      last if $pid == -1;
    
      warn "$0: unexpected kid $pid" unless $kids{$pid};
      delete $kids{$pid};
    }
    
    warn "$0: still running: ", join(", " => keys %kids), "\n"
      if keys %kids;
    
    print "$0: done!\n", `cat $log`;
    

    样本输出:

    [...]
    ./prog.pl:收割 child ...
    ./prog.pl:完成!
    用户A:步骤1
    用户 B:步骤 1
    用户 C:步骤 1
    用户 C:第 2 步
    用户 C:步骤 3
    用户 D:步骤 1
    用户 E:第 1 步
    用户F:步骤1
    用户 G:步骤 1
    用户 H:第 1 步
    用户 I:步骤 1
    用户J:第1步
    用户 D:步骤 2
    用户 D:第 3 步
    用户F:步骤2
    用户 G:第 2 步
    用户 H:第 2 步
    用户 I:步骤 2
    用户 I:步骤 3
    用户 B:步骤 2
    用户A:步骤2
    用户A:步骤3
    用户E:步骤2
    用户 F:第 3 步
    用户 G:步骤 3
    用户J:第2步
    用户J:第3步
    用户 E:第 3 步
    用户 H:步骤 3
    用户 B:步骤 3

    请记住,顺序将因运行而异。

    关于perl - 使用 Perl 并发追加到同一个文件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2365642/

    相关文章:

    perl - ack-grep : chars escaping

    perl - 使用 Perl 删除空行和空格

    Java:线程池如何将线程映射到可运行对象

    ASP.NET 开发服务器并发处理不起作用

    collections - pop 是否抛出异常?

    java - 文件锁定和删除

    javascript - 将数据传递给 CGI 脚本并使用 jQuery.ajax 返回

    perl - Linux、回溯、Perl、Bluesniff

    java - 对象级和类级锁定

    python - Tkinter 中的程序窗口锁定