Concurrent使用Perl附加到同一文件

Bri*_*anH 12 concurrency perl locking

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

现在,该脚本正在为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
Run Code Online (Sandbox Code Playgroud)

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

这样可以防止同一个文件被多次写入,但是该目录可以快速获取数千个文件(每个文件只包含几个字节).有一个旋转和压缩这些日志的过程,但是由于脚本每天只记录一个文件以减少正在创建的日志文件的数量,因此我不得不这样做.

基本上,日志文件将在文件名中包含当前日期,并且只要CGI脚本需要写入日志,它就会追加到当天的一个日志文件,无论用户或他们在哪个步骤.

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

我的问题是,处理并发附加到此日志文件的最佳方法是什么?在追加之前我需要锁定它吗?我发现Perl Monks上的这个页面似乎表明"当多个进程正在写入同一个文件时,并且所有这些进程都打开了附加文件,数据不会被覆盖."

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

摘要:

  • 并发附加到同一文件
  • 每个附加到文件的只有一行,少于50个字符
  • 订单无关紧要

谢谢!

Gre*_*con 13

是的,使用flock.

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

#! /usr/bin/perl

use warnings;
use strict;

use Fcntl qw/ :flock /;
Run Code Online (Sandbox Code Playgroud)

然后我们指定日志的路径和将运行的客户端数量:

my $log = "/tmp/my.log";
my $clients = 10;
Run Code Online (Sandbox Code Playgroud)

要记录消息,请以附加模式打开文件,以便所有写入最后自动进行.然后打电话flock等我们开启独家访问日志.一旦我们启动,请编写消息和close句柄,它会自动释放锁定.

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: $!";
}
Run Code Online (Sandbox Code Playgroud)

现在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;
}
Run Code Online (Sandbox Code Playgroud)

不要忘记等待所有孩子退出:

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`;
Run Code Online (Sandbox Code Playgroud)

样本输出:

[...]
./prog.pl: reaping children...
./prog.pl: done!
UserA: Step 1
UserB: Step 1
UserC: Step 1
UserC: Step 2
UserC: Step 3
UserD: Step 1
UserE: Step 1
UserF: Step 1
UserG: Step 1
UserH: Step 1
UserI: Step 1
UserJ: Step 1
UserD: Step 2
UserD: Step 3
UserF: Step 2
UserG: Step 2
UserH: Step 2
UserI: Step 2
UserI: Step 3
UserB: Step 2
UserA: Step 2
UserA: Step 3
UserE: Step 2
UserF: Step 3
UserG: Step 3
UserJ: Step 2
UserJ: Step 3
UserE: Step 3
UserH: Step 3
UserB: Step 3

请记住,订单将与运行不同.

  • gbacon这样做是正确的,但在调整他的代码时要记住一些重要事项:你*不要*解锁(`LOCK_UN`)文件 - 你关闭它.这将确保数据被刷新然后*然后*解锁它. (3认同)