Сейчас все работают с базами данных. И это, наверное, правильно. Однако, иногда, все-же приходится работать и со старыми добрыми плоскими файлами. Мы можем их открывать, закрывать, писать в них что-то, читать из них, а так же... блокировать. О блокировке файлов я сейчас и хочу поговорить.
Блокирование нужно для того, чтобы избежать ситуации, когда в один файл одновременно пишут несколько писателей, или читатель читает файл тогда, когда он кем-то пишется. Если такое происходит, то, в первом случае, мы получим кашу или потеряем данные, а во втором - неверно прочитаем данные.
В Perl есть функция flock, которая призвана блокировать файл. Однако, она не всегда делает то, что мы ожидаем от нее. Функция flock принимает два параметра: дескриптор открытого файла и режим блокировки. Подробнее о режимах блокировки можно прочитать в любом мануале</a>, а мы остановимся на том, что эта функция иногда дает сбои. Как же это происходит? Рассмотрим пример использования:
open(F, ">important_data.txt");
flock(F,2);
#...здесь что-то делаем с файлом...
close(F);
Сначала, мы открываем файл. Затем, блокируем его полностью на чтение и запись, отдавая в распоряжение текущему скрипту. Остальные файлы, использующие flock будут стоять в очереди, пока блокировка не снимется. А снимется она, при вызове close для блокированного файла.
Представим, что два процесса одновременно (!) открывают файл. При этом, они оба получают дескриптор, оба получают инструкции для удаления содержимого файла, до начала записи в него. Затем, они одновременно (!) вызывают flock. Один из них первым получает доступ к файлу, делает с ним свои дела, в то время, как другой честно стоит в очереди. Что происходит, когда первый процесс заканчивает работу с файлом? Второй получает доступ к нему, ОЧИЩАЕТ файл, потому что он открыт для перезаписи, пишет в него свои данные и закрывает. При этом, мы теряем данные, записанные первым скриптом. Кто виноват? Что делать?
Лично я, решаю проблему, эксплуатируя свои функции, которые прекрасно работают (правда, не со всеми прагмами. strict "refs" мешает). Вот оно:
sub myopen {
no strict "refs";
my ($descr, $mode, $file, $trash) = @_;#принимаем параметры
return 0 if ($descr eq 'SEMAFOR'); #желаемый дескриптор не должен быть 'SEMAFOR'
open(SEMAFOR, ">>semafor.txt") or return 0; #открываем файл семафора на дозапись
flock (SEMAFOR 2); #Блокируем файл семафора
open($descr, "$mode$file") or return 0; #открываем нужный файл
return $descr; #возвращаем дескриптор
}
sub myclose {
my ($descr) = @_; #принимаем дескриптор
close(SEMAFOR); #закрываем семафор
close($descr); #закрываем файл по дескриптору
}
И пример использования:
if (my $F = myopen("F",'>','data.txt')) {
no strict "refs";#не нужно, если не используется прагма strict
print $F 'hello!';
use strict "refs";#не нужно, если не планируется к использованию далее прагма strict
myclose($F);
print 'OK';
}
else {
print 'Error';
}
Без прагмы strict этот пример работает отлично. Если она все-таки включена, приходится отключать ее на время. Кроме того, мы выключаем эту прагму внутри функции открытия. Так как прагма действует только в пределах лексической области видимости, за пределами функции no strict "refs" распространяться не будет.
Чего же мы добились, открывая файл семафора, перед основным файлом? Мы добились того, что блокировка происходит ДО открытия нужного нам файла, а не после, как было в первом примере. Проблема решена! Правда, теперь нужно, чтобы все открытия файлов, используемые у вас в скрипте, были осуществлены через эти функции. Если какой-то скрипт-мерзавец, попытается открыть нужный файл напрямую, функцией open, то ничто уже не будет защищать этот файл от одновременного доступа.
Используйте файлы, относитесь к ним бережно и блокируйте, чтобы не баловали:)