Как заблокировать файл в Perl? - PullRequest
27 голосов
/ 29 августа 2008

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

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

Ответы [ 13 ]

28 голосов
/ 29 августа 2008

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

Некоторые полезные ссылки:

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

11 голосов
/ 08 октября 2011

Другие ответы довольно хорошо охватывают блокировку 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. Как правило, эти виды эмуляции работают плохо, и вам рекомендуется избегать их.

7 голосов
/ 01 сентября 2008

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

6 голосов
/ 17 сентября 2008

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

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

Так что не делай этого. Вместо этого 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 s успех.

5 голосов
/ 17 сентября 2008

Я думаю, что было бы намного лучше показать это с лексическими переменными в качестве обработчиков файлов. и обработка ошибок. Также лучше использовать константы из модуля 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 $!";

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

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

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

4 голосов
/ 17 сентября 2008

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

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

3 голосов
/ 08 октября 2008
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' - $!";
1 голос
/ 19 сентября 2014

Одной альтернативой подходу lock file является использование блокировки socket . См. Lock :: Socket в CPAN для такой реализации. Использование так же просто, как показано ниже:

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

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

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

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

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

1 голос
/ 13 июля 2012

Разработано из 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: $!";
}
1 голос
/ 17 сентября 2008

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

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);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...