Вопрос

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

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

Это было полезно?

Решение

Если вы в конечном итоге используете 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' - $!";

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

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

Другие советы

Другие ответы довольно хорошо охватывают блокировку 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 my $fh, '+<', 'test.dat'
    or die "Couldn’t open test.dat: $!\n";

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

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

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

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

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

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

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

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

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

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

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

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

Разработано на основе 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: $!";
}

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

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

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

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

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

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

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

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

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

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

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

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$!";
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top