¿Cómo locking un archivo en Perl?

¿Cuál es la mejor manera de crear un locking en un archivo en Perl?

¿Es mejor agruparse en el archivo o crear un archivo de locking para colocar un candado y comprobar si hay un candado en el archivo de locking?

Si terminas usando rebaño, aquí tienes un código para hacerlo:

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

Algunos enlaces útiles:

  • Tutorial de locking de archivos PerlMonks (algo antiguo)
  • documentación de flock()

En respuesta a su pregunta agregada, diría que coloque el candado en el archivo o cree un archivo que llame ‘bloquear’ siempre que el archivo esté bloqueado y elimínelo cuando ya no esté bloqueado (y luego asegúrese de que sus progtwigs obedezcan esa semántica).

Las otras respuestas cubren el locking de perl de Perl bastante bien, pero en muchos sistemas Unix / Linux, en realidad existen dos sistemas de locking independientes: BSD flock () y POSIX fcntl () basados ​​en lockings.

A menos que proporcione opciones especiales para configurar al construir Perl, su bandada utilizará flock () si está disponible. En general, esto es bueno y probablemente lo que desee si solo necesita bloquear dentro de su aplicación (se ejecuta en un solo sistema). Sin embargo, algunas veces necesita interactuar con otra aplicación que use lockings fcntl () (como Sendmail, en muchos sistemas) o quizás necesite bloquear archivos en sistemas de archivos montados en NFS.

En esos casos, es posible que desee ver File :: FcntlLock o File :: lockf . También es posible hacer un locking basado en fcntl () en Perl puro (con algunos bits peludos y no portátiles del paquete ()).

Descripción rápida de las diferencias flock / fcntl / lockf:

lockf casi siempre se implementa en la parte superior de fcntl, solo tiene locking de nivel de archivo. Si se implementa utilizando fcntl, las limitaciones a continuación también se aplican a lockf.

fcntl proporciona el locking de nivel de rango (dentro de un archivo) y el locking de red a través de NFS, pero los lockings no son heredados por procesos secundarios después de un fork (). En muchos sistemas, debe tener el manejador de archivos abierto de solo lectura para solicitar un locking compartido y leer y escribir para solicitar un locking exclusivo.

flock solo tiene locking de nivel de archivo, el locking solo se realiza en una sola máquina (puede bloquear un archivo montado en NFS, pero solo los procesos locales verán el locking). Los lockings son heredados por los niños (suponiendo que el descriptor de archivo no está cerrado).

En ocasiones, el rebaño (sistemas SYSV) se emula utilizando lockf o fcntl; en algunos sistemas BSD lockf se emula utilizando flock. En general, este tipo de emulación funciona mal y se recomienda evitarlas.

CPAN al rescate: IO :: LockedFile .

Ryan P escribió:

En este caso, el archivo está realmente desbloqueado durante un corto período de tiempo mientras se vuelve a abrir el archivo.

Entonces no hagas eso. En su lugar, open el archivo para lectura / escritura:

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

Cuando esté listo para escribir el contador, simplemente vuelva al inicio del archivo. Tenga en cuenta que si hace eso, debe truncate justo antes de close , de modo que el archivo no quede con basura arrastrada si sus nuevos contenidos son más cortos que los anteriores. (Por lo general, la posición actual en el archivo está al final, por lo que puede escribir truncate $fh, tell $fh ).

Además, tenga en cuenta que utilicé el manejador de archivos open y el archivo léxico, y también verifiqué el éxito de la operación. Evite los manejadores de archivos globales (¿las variables globales son malas, mmkay?) Y la open mágica de dos argumentos (que ha sido fuente de muchos errores de n (explotables) en el código de Perl), y siempre pruebe si su open es exitosa.

Creo que sería mucho mejor mostrar esto con variables léxicas como manejadores de archivos y manejo de errores. También es mejor usar las constantes del módulo Fcntl que codificar el número mágico 2, que podría no ser el número correcto en todos los sistemas operativos.

     use Fcntl ': flock';  # import LOCK_ * constantes

     # abrir el archivo para agregar
     abrir (mi $ fh, '>>', 'test.dat') o morir $ !;

     # intenta bloquear el archivo exclusivamente, esperará hasta que obtenga el locking
     rebaño ($ fh, LOCK_EX);

     # hacer algo con el archivo aquí (imprimirlo en nuestro caso)

     # en realidad no deberías desbloquear el archivo
     # cerrar el archivo lo desbloqueará
     cerrar ($ fh) o advertir "No se pudo cerrar el archivo $!";

Consulte la documentación completa de la bandada y el tutorial de locking de archivos en PerlMonks, aunque también use el estilo anterior de uso del manejador de archivos.

En realidad, generalmente omito el manejo de errores en close () ya que no hay mucho que pueda hacer si falla de todos modos.

En cuanto a qué bloquear, si está trabajando en un solo archivo, bloquee ese archivo. Si necesita bloquear varios archivos a la vez, entonces – para evitar lockings muertos – es mejor elegir un archivo que está bloqueando. Realmente no importa si ese es uno de los varios archivos que realmente necesita bloquear o un archivo separado que crea solo para el propósito de locking.

¿Has considerado utilizar el módulo LockFile :: Simple ? Ya hace la mayor parte del trabajo por ti.

En mi experiencia pasada, he encontrado que es muy fácil de usar y robusto.

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

Mi objective en esta pregunta era bloquear un archivo que se utiliza como un almacén de datos para varios scripts. Al final, utilicé un código similar al siguiente (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 

En su ejemplo, eliminé el flock FILE, 8 ya que el close (FILE) también realiza esta acción. El verdadero problema fue cuando el script comienza tiene que contener el contador actual, y cuando termina tiene que actualizar el contador. Aquí es donde Perl tiene un problema, para leer el archivo usted:

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

Ahora quiero escribir los resultados y, dado que quiero sobrescribir el archivo, necesito volver a abrir y truncar, lo que da como resultado lo siguiente:

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

En este caso, el archivo está realmente desbloqueado durante un corto período de tiempo mientras se vuelve a abrir el archivo. Esto demuestra el caso del archivo de locking externo. Si va a cambiar los contextos del archivo, use un archivo de locking. El 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); 

Desarrollado fuera 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: $!"; } 

Una alternativa al enfoque de archivo de locking es usar un socket de locking. Ver Lock :: Socket en CPAN para tal implementación. El uso es tan simple como lo siguiente:

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

Hay un par de ventajas al usar un socket:

  • garantizado (a través del sistema operativo) que no hay dos aplicaciones que mantengan el mismo locking: no hay condición de carrera.
  • garantizado (de nuevo a través del sistema operativo) para limpiar claramente cuando su proceso finaliza, por lo que no hay lockings añejos para hacer frente.
  • se basa en una funcionalidad que es bien soportada por cualquier cosa en la que Perl se ejecute: sin problemas con el soporte de flock (2) en Win32, por ejemplo.

La desventaja obvia es, por supuesto, que el espacio de nombres de locking es global. Es posible un tipo de denegación de servicio si otro proceso decide bloquear el puerto que necesita.

[revelación: soy el autor del módulo mencionado anteriormente]

Usa el rebaño Luke.

Editar: Esta es una buena explicación.

flock crea lockings de archivos de estilo Unix, y está disponible en la mayoría de las ejecuciones de Perl del sistema operativo. Sin embargo, los lockings de bandadas son solo de asesoramiento.

editar: enfatizaron que el rebaño es portátil

Aquí está mi solución para leer y escribir en una cerradura …

 open (TST,"+< readwrite_test.txt") or die "Cannot open file\n$!"; flock(TST, LOCK_EX); # Read the file: @LINES=; # 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$!";