Блокировка файла
Проблема
Несколько процессов одновременно пытаются обновить один и тот же файл.
Решение
Организуйте условную блокировку с помощью функции 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; большинство не поддерживает. На практике
вам не удастся написать код, работающий в любой системе.
См. также
Proverte kod v komentariyah gde pro list tam oshibki detskie
Оставить комментарий:
|
|