Блокировка файла

Проблема

Несколько процессов одновременно пытаются обновить один и тот же файл.

Решение

Организуйте условную блокировку с помощью функции flock:
open(FH, "+< $path)           or die "can't open $path: $!";
flock(FH, 2)                   or die "can't flock $path: $!";
# Обновить файл, затем...
close(FH)                     or die "can't close $path: $!";

Комментарий

Операционные системы сильно отличаются по типу и степени надежности используемых механизмов блокировки. Perl старается предоставить программисту рабочее решение даже в том случае, если операционная система использует другой базовый механизм. Функция flock получает два аргумента: файловый манипулятор и число, определяющее возможные действия с данным манипулятором Числа обычно представлены символьными константами типа LОСК_ЕХ, имена которых можно получить из модуля Fcntl или IO::File.
Символические константы LOCK_SH, LOCK_EX, LOCK_UN и LOCK_NB появились в модуле Fcntl лишь начиная с версии 5.004, но даже теперь они доступны лишь по специальному запросу с тегом :flock. Они равны соответственно 1, 2, 4 и 8, и эти значения можно использовать вместо символических констант. Нередко встречается следующая запись:
sub LOCK_SH()   {1}          # Совместная блокировка (для чтения)
sub LOCK_EX()   {2}          # Монопольная блокировка (для записи)
sub LOCK_NB()   {4}          # Асинхронный запрос блокировки
sub LOCK_UN()   {8}          # Снятие блокировки (осторожно!)
Блокировки делятся на две категории: совместные (shared) и монопольные (exclusive). Термин «монопольный» может ввести вас в заблуждение, поскольку процессы не обязаны соблюдать блокировку файлов. Иногда говорят, что flock реализует условную блокировку, чтобы операционная система могла приостановить все операции записи в файл до того момента, когда с ним закончит работу последний процесс чтения.
Условная блокировка напоминает светофор на перекрестке. Светофор работает лишь в том случае, если люди обращают внимание на цвет сигнала: красный пли зеленый — или желтый для условной блокировки. Красный цвет не останавливает движение; он всего лишь сообщает, что движение следует прекратить. Отчаянный, невежественный или просто наглый водитель проедет через перекресток независимо от сигнала светофора. Аналогично работает и функция flock — она тоже блокирует другие вызовы flock, а не процессы, выполняющие ввод/вывод. Правила должны соблюдаться всеми, иначе могут произойти (и непременно произойдут) несчастные случаи.
Добропорядочный процесс сообщает о своем намерении прочитать данные из файла, запрашивая блокировку LOCK_SH. Совместная блокировка файла может быть установлена сразу несколькими процессами, поскольку они (предположительно) не будут изменять данные. Если процесс собирается произвести запись в файл, он должен запросить монопольную блокировку с помощью LOCK_ЕХ. Затем операционная система приостанавливает этот процесс до снятия блокировок остальными процессами, после чего приостановленный процесс получает блокировку и продолжает работу. Можно быть уверенным в том, что на время сохранения блокировки никакой другой процесс не сможет выполнить flock(FH, LOCK_EX) для того же файла. Это похоже на другое утверждение — «в любой момент для файла может быть установлена лишь одна монопольная блокировка», но не совсем эквивалентно ему. В некоторых системах дочерние процессы, созданные функцией fork, наследуют от своих родителей не только открытые файлы, но и установленные блокировки. Следовательно, при наличии монопольной блокировки и вызове fork без ехес производный процесс может унаследовать монопольную блокировку файла.
Функция flock по умолчанию приостанавливает процесс. Указывая флаг LOCK_NB, при запросе можно получить блокировку без приостановки. Благодаря этому можно предупредить пользователя об ожидании снятия блокировок другими процессами:
unless (flock(FH, LOCK_EX|LOCK_NB)) {
  warn "can't immediately write-lock the file ($!), blocking ...";
  unless (flock(FH, LOCK_EX)) {
    die "can't get write-lock on numfile: $!";
  }
}
Если при использовании LOCK_NB вам было отказано в совместной блокировке, следовательно, кто-то другой получил LOCK_EX и обновляет файл. Отказ в монопольной блокировке означает, что другой процесс установил совместную или монопольную блокировку, поэтому пытаться обновлять файл не следует.
Блокировки исчезают с закрытием файла, что может произойти лишь после завершения процесса. Ручное снятие блокировки без закрытия файла — дело рискованное. Это связано с буферизацией. Если между снятием блокировки и очисткой буфера проходит некоторое время, то данные, заменяемые содержимым буфера, могут быть прочитаны другим процессом. Более надежный путь выглядит так:
if ($] < 5.004) {        # Проверить версию Perl
  my $old_fh = select(FH);
  local $|=1;               # Разрешить буферизацию команд
  local $\ = '';            # Очистить разделитель выходных записей
  print "";                 # Вызвать очистку буфера
  select($old_fh);          # Восстановить предыдущий манипулятор
}
flock(FH, LOCK_UN);
До появления Perl версии 5.004 очистку буфера приходилось выполнять принудительно. Программисты часто забывали об этом, поэтому в 5.004 снятие блокировки изменилось так, чтобы несохраненные буферы очищались непосредственно перед снятием блокировки.
А вот как увеличить число в файле с применением flock:
use Fcntl qw(:DEFAULT :flock);
sysopen(FH, "numfile", 0_RDWR|0_CREAT)
                              or die "can't open numfile: $!";
flock(FH, LOCK_EX)            or die "can't write-lock numfile: $!";
# Блокировка получена, можно выполнять ввод/вывод
$num = <FH> || 0;             # НЕ ИСПОЛЬЗУЙТЕ "or" ! !
seek(FH, 0, 0)                or die "can't rewind numfile : $!'
truncate(FH, 0)               or die "can't truncate numfile; $!
print FH $num+1, "\n"         or die "can't write numfile: $!";
close(FH)                     or die "can't close numfile: $!";
Закрытие файлового манипулятора приводит к очистке буферов и снятию блокировки с файла.
С блокировкой файлов дело обстоит сложнее, чем можно подумать — и чем нам хотелось бы. Блокировка имеет условный характер, поэтому если один процесс использует ее, а другой — нет, все идет прахом. Никогда не используйте факт существования файла в качестве признака блокировки, поскольку между проверкой существования и созданием файла может произойти вмешательство извне. Более того, блокировка файлов подразумевает концепцию состояния и потому не соответствует моделям некоторых сетевых файловых систем — например, NFS. Хотя некоторые разработчики утверждают, что fcntl решает эти проблемы, практический опыт говорит об обратном.
В блокировках NFS участвует как сервер, так и клиент. Соответственно, нам не известен общий механизм, гарантирующий падежную блокировку в NFS. Это возможно в том случае, если некоторые операции заведомо имеют атомарный характер в реализации сервера или клиента. Это возможно, если и сервер, и клиент поддерживают flock или fcntl; большинство не поддерживает. На практике вам не удастся написать код, работающий в любой системе.

См. также




2013-09-10 17:05:19

Proverte kod v komentariyah gde pro list tam oshibki detskie




Оставить комментарий:
Ваше Имя:
Email:
Антибот: *  
Ваш комментарий: