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

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

Решение
Вызовите функцию flock() для выполнения консультативной блокировки:

$fh = fopen('guestbook.txt','a') or die($php_errormsg);
flock($fh,LOCK_EX) or die($php_errormsg);
fwrite($fh,$_REQUEST['guestbook_entry']) or die($php_errormsg);
fflush($fh) or die($php_errormsg);
flock($fh,LOCK_UN) or die($php_errormsg);
fclose($fh) or die($php_errormsg);

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

С помощью функции flock() можно устанавливать блокировку двух типов: монопольную и разделяемую.


Монопольная блокировка (exclusive lock), определяемая константой LOCK_EX в качестве второго аргумента функции flock(), может быть удержана одновременно для конкретного файла только одним процессом. Разделяемая блокировка (shared lock), определяемая константой LOCK_SH, может быть удержана одновременно для конкретного файла более чем одним процессом. Перед записью в файл необходимо установить монопольную блокировку. Перед чтением из файла необходимо установить разделяемую блокировку.

Чтобы разблокировать файл, вызовите функцию flock() со значением LOCK_UN в качестве второго аргумента. Важно сбросить все данные буфера, которые должны быть записаны в файл, до того, как файл будет разблокирован. Другие процессы не получат возможность блокировки, пока данные не будут записаны.

По умолчанию функция flock() блокирует, если имеет такую возможность. Чтобы приказать ей не блокировать, добавьте ко второму аргументу значение LOCK_NB:

$fh = fopen('guestbook.txt','a') or die($php_errormsg);
$tries = 3;
while ($tries > 0) {
$locked = flock($fh,LOCK_EX | LOCK_NB);
if (! $locked) {
sleep(5);
$tries--;
} else {
// не выполнять цикл повторно
$tries = 0;
}
}
if ($locked) {
fwrite($fh,$_REQUEST['guestbook_entry']) or die($php_errormsg);
fflush($fh) or die($php_errormsg);
flock($fh,LOCK_UN) or die($php_errormsg);
fclose($fh) or die($php_errormsg);
} else {
print "Can't get lock.";
}

В режиме отмены блокировки функция flock() выходит немедленно, даже если не может получить доступ к блокировке.


Предыдущий пример трижды пытается получить доступ к блокировке файла guestbook.txt, ожидая пять секунд перед каждой попыткой. Блокирование с помощью функции flock() работает не при всех условиях, примером тому служат некоторые реализации NFS. Кроме того, flock() не поддерживается в Windows 95, 98 или ME. Для эмулирования блокировки файла в таких случаях используется каталог в качестве индикатора монопольной блокировки. Это отдельный пустой каталог, наличие которого означает, что файл данных заблокирован. Перед открытием файла с данными создайте каталог блокировки, а по окончании работы с файлом данных удалите этот каталог. Как показано ниже, во всем остальном программа доступа к файлу выглядит так же:

$fh = fopen('guestbook.txt','a') or die($php_errormsg);
// выполняем цикл, пока не создадим каталог блокировки
$locked = 0;
while (! $locked) {
if (@mkdir('guestbook.txt.lock',0777)) {
$locked = 1;
} else {
sleep(1);
}
}
if (-1 == fwrite($fh,$_REQUEST['guestbook_entry'])) {
rmdir('guestbook.txt.lock');
die($php_errormsg);
}
if (! fclose($fh)) {
rmdir('guestbook.txt.lock');
die($php_errormsg);
}
rmdir('guestbook.txt.lock') or die($php_errormsg);

Для индикации блокировки вместо файла используется каталог, поскольку функция mkdir() не в состоянии создать каталог, если он уже существует.


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

Если в качестве индикатора выступает файл, то код для его создания выглядит так:

$locked = 0;
while (! $locked) {
if (! file_exists('guestbook.txt.lock')) {
touch('guestbook.txt.lock');
$locked = 1;
} else {
sleep(1);
}
}

При большом трафике из этого может ничего не получиться, поскольку сначала с помощью функции file_exists() проверяется существование блокировки, а затем с помощью функции touch() создается блокировка. После того как один процесс вызвал функцию file_exists(),
другой процесс может вызвать функцию touch() до того, как ее вызовет первый процесс. В этом случае оба процесса будут считать, что они получили исключительный (монопольный) доступ к файлу, хотя ни один из них этого не сделал. В случае применения функции mkdir() нет паузы между проверкой существования и созданием, поэтому процессу, который создает каталог, гарантирован исключительный доступ.

Оцените статью: (0 голосов)
0 5 0

Статьи из раздела PHP на эту тему:
Выбор случайной строки из файла
Запись в несколько файловых дескрипторов одновременно
Запись в стандартный поток вывода
Непосредственная модификация файла без временной копии
Обработка каждого слова в файле

Вернуться в раздел: PHP / 18. Файлы