flock()
Функция flock() — встроенная функция PHP для блокировки файлов. Предотвращает одновременный доступ нескольких процессов и состояния гонки.
Что такое функция flock()?
Функция flock() выполняет блокировку файлов в PHP. Блокировка позволяет одному процессу сообщить остальным: «Я работаю с этим файлом — подождите своей очереди». Без неё два скрипта, выполняющихся одновременно, могут перемежать свои операции записи и повредить файл. Это классическое состояние гонки, и flock() — простейший инструмент PHP для его предотвращения.
На этой странице описаны назначение функции, типы блокировок, полный рабочий пример, распространённые подводные камни и место flock() среди других файловых функций PHP.
О «рекомендательной» блокировке
В Unix-подобных системах flock() является рекомендательной: блокировка соблюдается только теми процессами, которые также вызывают flock() для того же файла. Программа, игнорирующая блокировки, по-прежнему может свободно читать файл или перезаписывать его. Таким образом, блокировка защищает только в том случае, если все скрипты, работающие с файлом, её соблюдают. В Windows блокировка является обязательной (принудительно применяется ОС), поэтому поведение немного различается на разных платформах — не полагайтесь на принудительное применение в переносимом коде.
Синтаксис
flock($stream, $operation, &$would_block = null): bool| Параметр | Описание |
|---|---|
$stream | Указатель на файл, возвращённый функцией fopen(). |
$operation | Одна из констант блокировки ниже, при необходимости объединённая с LOCK_NB через OR. |
$would_block | Необязательный. Устанавливается в 1, если блокировка вызвала бы ожидание (имеет смысл только с LOCK_NB). Передаётся по ссылке. |
Функция возвращает true при успехе или false при ошибке.
Типы блокировок
| Константа | Значение |
|---|---|
LOCK_SH | Разделяемая блокировка (читатель). Несколько процессов могут удерживать разделяемую блокировку одновременно, но ни один из них не может удерживать эксклюзивную. Используйте при чтении. |
LOCK_EX | Эксклюзивная блокировка (писатель). Только один процесс может её удерживать; все остальные — читатели и писатели — ждут. Используйте при записи. |
LOCK_UN | Снять блокировку, удерживаемую на потоке. |
LOCK_NB | Модификатор неблокирующего режима. Объедините его с LOCK_SH или LOCK_EX (например, `LOCK_EX |
По умолчанию flock() блокирует: если другой процесс удерживает эксклюзивную блокировку, ваш вызов ждёт, пока блокировка не освободится. Добавьте LOCK_NB, если предпочитаете немедленный отказ вместо ожидания.
Как использовать функцию flock()
Паттерн всегда состоит из одних и тех же четырёх шагов:
- Откройте файл с помощью
fopen(), используя режим, соответствующий выполняемой операции ('r+','c','a', …). - Установите блокировку с помощью
flock($file, LOCK_EX)(илиLOCK_SHдля чтения). - Прочитайте или запишите файл.
- Снимите блокировку с помощью
flock($file, LOCK_UN)и закройте файл с помощьюfclose().
Полный рабочий пример
Этот скрипт открывает файл-счётчик, устанавливает эксклюзивную блокировку, увеличивает хранящееся число и записывает его обратно — это классическая операция чтение-изменение-запись, которая обязательно должна выполняться под блокировкой для безопасности при конкурентном доступе:
<?php
$filename = 'counter.txt';
// 'c' opens for read/write, creating the file if missing,
// and does NOT truncate it (unlike 'w').
$file = fopen($filename, 'c+');
if ($file === false) {
exit("Could not open file.\n");
}
if (flock($file, LOCK_EX)) { // block until we hold the lock
$current = (int) stream_get_contents($file);
$current++;
rewind($file); // back to the start
ftruncate($file, 0); // clear old contents
fwrite($file, (string) $current);
fflush($file); // push to disk before unlocking
flock($file, LOCK_UN); // release the lock
echo "Counter is now: $current\n";
} else {
echo "Could not acquire lock.\n";
}
fclose($file);При трёхкратном запуске выводится Counter is now: 1, затем 2, затем 3. Поскольку операция чтение-увеличение-запись выполняется под LOCK_EX, два процесса никогда не смогут прочитать одно и то же значение и оба записать 2.
Немедленный отказ с неблокирующей блокировкой
Когда ожидание нежелательно — например, для задания cron, которое должно пропустить выполнение, если предыдущее ещё не завершилось — объедините LOCK_EX с LOCK_NB:
<?php
$file = fopen('job.lock', 'c');
if (flock($file, LOCK_EX | LOCK_NB)) {
echo "Got the lock, doing work...\n";
// ... long-running task ...
flock($file, LOCK_UN);
} else {
echo "Another instance is already running. Exiting.\n";
}
fclose($file);Распространённые подводные камни
- Блокировки привязаны к открытому дескриптору файла, а не к пути. Двукратный вызов
fopen()для одного и того же файла даёт два независимых дескриптора, и блокировка одного не блокирует другой в том же процессе. - Используйте
'c'/'c+', а не'w', при блокировке. Режим'w'усекает файл в момент открытия — до получения блокировки — что сводит на нет всю цель. Выполняйте усечение явно с помощьюftruncate()уже после получения блокировки. flock()ненадёжно работает через NFS и некоторые сетевые файловые системы или FAT. Для координации между несколькими серверами используйте настоящий сервис блокировок (блокировка строки в базе данных, Redis и т. д.).fclose()снимает все оставшиеся блокировки, но лучше явно снять блокировку с помощьюLOCK_UN, чтобы файл стал доступен сразу после завершения работы с ним.
Заключение
flock() — встроенный инструмент PHP для управления конкурентным доступом к файлу. Используйте LOCK_EX при записи, LOCK_SH при чтении и LOCK_NB, если предпочитаете немедленный отказ вместо ожидания. Помните, что блокировка в Unix носит рекомендательный характер — она защищает только тогда, когда все скрипты, работающие с файлом, её соблюдают. Для более широких файловых операций смотрите fwrite(), fread() и file_put_contents().