Pergunta

Qual é a melhor maneira de criar um bloqueio em um arquivo em Perl?

É melhor agrupar o arquivo ou criar um arquivo de bloqueio para colocar um bloqueio e verificar se há bloqueio no arquivo de bloqueio?

Foi útil?

Solução

Se você acabar usando o rebanho, aqui está um código para fazer isso:

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' - $!";

Alguns links úteis:

Em resposta à sua pergunta adicional, eu diria que coloque o bloqueio no arquivo ou crie um arquivo que você chama de 'bloqueio' sempre que o arquivo estiver bloqueado e exclua-o quando não estiver mais bloqueado (e então certifique-se de que seus programas obedeçam essa semântica).

Outras dicas

As outras respostas cobrem muito bem o bloqueio de rebanho Perl, mas em muitos sistemas Unix/Linux existem na verdade dois sistemas de bloqueio independentes:Bloqueios baseados em BSD rebanho() e POSIX fcntl().

A menos que você forneça opções especiais para configuração ao construir Perl, seu rebanho usará rebanho() se disponível.Geralmente, isso é bom e provavelmente é o que você deseja se precisar apenas de bloqueio em seu aplicativo (executando em um único sistema).No entanto, às vezes você precisa interagir com outro aplicativo que usa bloqueios fcntl() (como Sendmail, em muitos sistemas) ou talvez você precise bloquear arquivos em sistemas de arquivos montados em NFS.

Nesses casos, você pode querer olhar para Arquivo::FcntlLock ou Arquivo::lockf.Também é possível fazer bloqueio baseado em fcntl() em Perl puro (com alguns pedaços de pack() complicados e não portáteis).

Visão geral rápida das diferenças de rebanho/fcntl/lockf:

lockf é quase sempre implementado sobre fcntl, possui apenas bloqueio em nível de arquivo.Se implementado usando fcntl, as limitações abaixo também se aplicam ao lockf.

fcntl fornece bloqueio em nível de intervalo (dentro de um arquivo) e bloqueio de rede por NFS, mas os bloqueios não são herdados por processos filhos após um fork().Em muitos sistemas, você deve ter o identificador de arquivo aberto somente leitura para solicitar um bloqueio compartilhado e leitura-gravação para solicitar um bloqueio exclusivo.

O rebanho possui apenas bloqueio em nível de arquivo, o bloqueio ocorre apenas dentro de uma única máquina (você pode bloquear um arquivo montado em NFS, mas apenas processos locais verão o bloqueio).Os bloqueios são herdados pelos filhos (assumindo que o descritor de arquivo não esteja fechado).

Às vezes (sistemas SYSV) o rebanho é emulado usando lockf ou fcntl;em alguns sistemas BSD, lockf é emulado usando rebanho.Geralmente esses tipos de emulação funcionam mal e é aconselhável evitá-los.

CPAN para o resgate: IO::LockedFile.

Ryan P escreveu:

Nesse caso, o arquivo é realmente desbloqueado por um curto período de tempo enquanto é reaberto.

Então não faça isso.Em vez de, open o arquivo para leitura/gravação:

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

Quando estiver pronto para escrever o contador, basta seek de volta ao início do arquivo.Observe que se você fizer isso, você deve truncate pouco antes close, para que o arquivo não fique com lixo se seu novo conteúdo for menor que os anteriores.(Normalmente, a posição atual no arquivo está no final, então você pode simplesmente escrever truncate $fh, tell $fh.)

Além disso, observe que usei três argumentos open e um identificador de arquivo léxico, e também verifiquei o sucesso da operação.Por favor, evite identificadores de arquivos globais (variáveis ​​globais são ruins, ok?) E magia de dois argumentos open (que tem sido uma fonte de muitos bugs (exploráveis) no código Perl), e sempre teste se seu opené bem sucedido.

Eu acho que seria muito melhor mostrar isso com variáveis ​​lexicais como manipuladores de arquivos e manuseio de erros.Também é melhor usar as constantes do módulo Fcntl do que codificar o número mágico 2, que pode não ser o número correto em todos os sistemas operacionais.

    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 $!";

Confira a íntegra documentação do rebanho e a Tutorial de bloqueio de arquivo no PerlMonks, embora isso também use o estilo antigo de uso de identificador de arquivo.

Na verdade, eu geralmente pulo o manuseio de erros em Close (), pois não há muito que eu possa fazer se falhar de qualquer maneira.

Em relação ao que bloquear, se você estiver trabalhando em um único arquivo, bloqueie esse arquivo.Se você precisar bloquear vários arquivos de uma vez - para evitar bloqueios mortos - é melhor escolher um arquivo que você está bloqueando.Realmente não importa se esse é um dos vários arquivos que você realmente precisa bloquear ou um arquivo separado que você cria apenas para fins de bloqueio.

Você já pensou em usar o LockFile::Módulo simples?Ele já faz a maior parte do trabalho para você.

Em minha experiência anterior, achei muito fácil de usar e resistente.

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' - $!";

Meu objetivo nesta questão era bloquear um arquivo usado como armazenamento de dados para vários scripts.No final, usei um código semelhante ao seguinte (de Chris):

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

No exemplo dele, removi o rebanho FILE, 8, pois close(FILE) também executa esta ação.O verdadeiro problema é que quando o script é iniciado, ele precisa manter o contador atual e, quando termina, precisa atualizar o contador.É aqui que o Perl tem um problema, para ler o arquivo você:

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

Agora quero escrever os resultados e como quero sobrescrever o arquivo, preciso reabrir e truncar, o que resulta no seguinte:

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

Nesse caso, o arquivo é realmente desbloqueado por um curto período de tempo enquanto é reaberto.Isso demonstra o caso do arquivo de bloqueio externo.Se você for alterar os contextos do arquivo, use um arquivo de bloqueio.O código modificado:

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);

Desenvolvido a partir de http://metacpan.org/pod/File::FcntlLock

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\n" 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: $!";
}

Uma alternativa ao bloqueio arquivo abordagem é usar um bloqueio tomada.Ver Bloqueio:: Soquete no CPAN para tal implementação.O uso é tão simples quanto o seguinte:

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

Existem algumas vantagens em usar um soquete:

  • garantido (através do sistema operacional) que dois aplicativos não manterão o mesmo bloqueio:não há condição de corrida.
  • garantido (novamente por meio do sistema operacional) para limpar perfeitamente quando o processo for encerrado, para que não haja bloqueios obsoletos com os quais lidar.
  • depende de funcionalidades que são bem suportadas por qualquer coisa em que Perl seja executado:não há problemas com suporte a rebanho(2) no Win32, por exemplo.

A desvantagem óbvia é que o namespace de bloqueio é global.É possível ocorrer uma espécie de negação de serviço se outro processo decidir bloquear a porta que você precisa.

[divulgação:Eu sou o autor do módulo mencionado]

Use o rebanho Lucas.

Editar: Esse é uma boa explicação.

O rebanho cria bloqueios de arquivo no estilo Unix e está disponível na maioria dos sistemas operacionais em que Perl é executado.No entanto, os bloqueios do rebanho são apenas consultivos.

editar:enfatizou que o rebanho é portátil

Aqui está minha solução para ler e escrever em um cadeado...

open (TST,"+< readwrite_test.txt") or die "Cannot open file\n$!";
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!\n";
$LINES[3]="Gekke henkie!\n";
# Write the file:
foreach $l (@LINES)
{
   print TST $l;
}
close(TST) or die "Cannot close file\n$!";
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top