Как заблокировать файл в Perl?

Каков наилучший способ создать блокировку файла в Perl?

Лучше всего скопировать файл или создать файл блокировки, чтобы установить блокировку и проверить блокировку файла блокировки?

13 ответов

Решение

Если вы в конечном итоге используете flock, вот код для этого:

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

Несколько полезных ссылок:

В ответ на ваш добавленный вопрос я бы сказал, что нужно либо установить блокировку на файл, либо создать файл, который вы называете "блокировать", когда файл заблокирован, и удалить его, когда он больше не заблокирован (а затем убедиться, что ваши программы подчиняются эта семантика).

Другие ответы довольно хорошо охватывают блокировку Perl-флок, но во многих системах Unix/Linux на самом деле есть две независимые системы блокировки: BSD-flock() и POSIX-блокировки на основе fcntl().

Если вы не предоставите специальные опции для настройки при сборке Perl, его стадо будет использовать flock(), если доступно. Как правило, это нормально и, вероятно, то, что вы хотите, если вам просто нужна блокировка в вашем приложении (работающем в одной системе). Однако иногда вам нужно взаимодействовать с другим приложением, которое использует блокировки fcntl() (например, Sendmail, во многих системах) или, возможно, вам необходимо выполнить блокировку файлов в файловых системах, смонтированных в NFS.

В этих случаях вы можете посмотреть на File::FcntlLock или File::lockf. Также возможно сделать блокировку на основе fcntl() в чистом Perl (с некоторыми непостоянными и непереносимыми битами pack()).

Краткий обзор отличий flock/fcntl/lockf:

lockf почти всегда реализуется поверх fcntl, имеет только блокировку на уровне файлов. Если реализовано с использованием fcntl, нижеприведенные ограничения также применяются к lockf.

fcntl обеспечивает блокировку на уровне диапазона (в файле) и сетевую блокировку по NFS, но блокировки не наследуются дочерними процессами после fork(). Во многих системах у вас должен быть файловый дескриптор, открытый только для чтения, чтобы запросить общую блокировку, и чтение-запись, чтобы запросить эксклюзивную блокировку.

У flock есть только блокировка на уровне файлов, блокировка только на одном компьютере (вы можете заблокировать смонтированный по NFS файл, но только локальные процессы увидят блокировку). Блокировки наследуются детьми (при условии, что дескриптор файла не закрыт).

Иногда (системы SYSV) flock эмулируется с помощью lockf или fcntl; в некоторых системах BSD lockf эмулируется с помощью flock. Как правило, эти виды эмуляции работают плохо, и вам рекомендуется избегать их.

CPAN на помощь: IO:: LockedFile.

Райан П написал:

В этом случае файл фактически разблокируется на короткий период времени, пока файл открывается снова.

Так что не делай этого. Вместо, open файл для чтения / записи:

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

Когда вы будете готовы написать счетчик, просто seek вернуться к началу файла. Обратите внимание, что если вы делаете это, вы должны truncate как раз перед close, так что файл не остается с завершающим мусором, если его новое содержимое короче, чем его предыдущие. (Обычно текущая позиция в файле находится в конце, поэтому вы можете просто написать truncate $fh, tell $fh.)

Также обратите внимание, что я использовал три аргумента open и лексический дескриптор файла, и я также проверил успешность операции. Пожалуйста, избегайте глобальных файловых дескрипторов (глобальные переменные плохие, mmkay?) И магических аргументов open (которая была источником многих (n уязвимых) ошибок в коде Perl), и всегда проверяйте, openс успехом.

Я думаю, что было бы намного лучше показать это с лексическими переменными в качестве обработчиков файлов и обработки ошибок. Также лучше использовать константы из модуля Fcntl, чем жестко кодировать магическое число 2, которое может быть неправильным числом во всех операционных системах.

    используйте Fcntl ':flock'; # import LOCK_* константы

    # открыть файл для добавления
    открыть (my $fh, '>>', 'test.dat') или умереть $!;

    # попытаться заблокировать файл исключительно, будет ждать, пока вы не получите блокировку
    стая ($ fh, LOCK_EX);

    # сделать что-то с файлом здесь (в нашем случае распечатать его)

    # на самом деле вы не должны разблокировать файл
    # закрыть файл разблокирует его
    закрыть ($fh) или предупредить "Не удалось закрыть файл $!";

Ознакомьтесь с полной документацией по flock и руководством по блокировке файлов в PerlMonks, хотя в нем также используется старый стиль использования дескрипторов файлов.

На самом деле я обычно пропускаю обработку ошибок при close(), так как я мало что могу сделать, если она все равно не удастся.

Что касается блокировки, если вы работаете в одном файле, заблокируйте этот файл. Если вам нужно заблокировать несколько файлов одновременно, то, чтобы избежать мертвых блокировок, лучше выбрать один файл, который вы блокируете. На самом деле не имеет значения, является ли это один из нескольких файлов, которые вам действительно нужно заблокировать, или отдельный файл, который вы создаете только для целей блокировки.

Рассматривали ли вы использование модуля LockFile:: Simple? Он уже делает большую часть работы за вас.

В моем прошлом опыте я нашел, что это очень легко и крепко использовать.

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

Разработано на http://metacpan.org/pod/File%3A%3AFcntlLock

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

Одной альтернативой подходу файла блокировки является использование сокета блокировки. Посмотрите Lock:: Socket на CPAN для такой реализации. Использование так же просто, как следующее:

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

Есть несколько преимуществ использования сокета:

  • гарантируется (через операционную систему), что никакие два приложения не будут удерживать одну и ту же блокировку: нет условия гонки.
  • гарантированная (опять же через операционную систему) аккуратная очистка при выходе из вашего процесса, так что нет никаких устаревших блокировок, чтобы иметь дело с.
  • полагается на функциональность, которая хорошо поддерживается всем, на чем работает Perl: например, нет проблем с поддержкой flock(2) в Win32.

Очевидным недостатком является, конечно, то, что пространство имен блокировки является глобальным. Для отказа в обслуживании возможно, если другой процесс решит заблокировать нужный вам порт.

[раскрытие: я являюсь автором упомянутого модуля]

Моей целью в этом вопросе было заблокировать файл, используемый в качестве хранилища данных для нескольких сценариев. В конце я использовал код, подобный следующему (от Криса):

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

В его примере я удалил стаю FILE, 8, поскольку close(FILE) также выполняет это действие. Реальная проблема заключалась в том, что когда скрипт запускается, он должен держать текущий счетчик, а когда он заканчивается, он должен обновлять счетчик. Вот где Perl имеет проблему, чтобы прочитать файл, который вы:

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

Теперь я хочу выписать результаты, и поскольку я хочу перезаписать файл, мне нужно снова открыть и обрезать, что приводит к следующему:

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

В этом случае файл фактически разблокируется на короткий период времени, пока файл открывается снова. Это демонстрирует случай для файла внешней блокировки. Если вы собираетесь изменять контексты файла, используйте файл блокировки. Модифицированный код:

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

Вот мое решение для чтения и записи в одном замке...

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

Flock, вероятно, лучший, но требует, чтобы вы написали весь вспомогательный код вокруг него - таймауты, устаревшие блокировки, несуществующие файлы и т. Д. Я исправил LockFile::Simple, но обнаружил, что он начал устанавливать umask по умолчанию только для чтения и не очищал это. В результате возникают проблемы со случайными разрешениями в многопроцессорном / многопоточном приложении на modperl. Я решил заключить NFSLock в оболочку с некоторой обработкой пустых файлов.

Используйте стадо, Люк.

Изменить: это хорошее объяснение.

flock создает блокировки файлов в стиле Unix и доступен на большинстве ОС, на которых работает Perl. Однако замки стада носят рекомендательный характер.

редактировать: подчеркнул, что стадо переносимо

Другие вопросы по тегам