`LOCK_EX` запрещает чтение, но не запись?
Почему я не могу прочитать файл заблокированный с LOCK_EX
? Я все еще могу написать ему.
Я хотел знать, что происходит, если один процесс блокирует файл (с LOCK_SH
или же LOCK_EX
) и другой процесс пытается прочитать или записать этот файл, но игнорирует блокировку вообще. Итак, я сделал небольшой скрипт, который имеет 3 функции:
- Блокировка: открывает целевой файл, записывает в него, блокирует файл (с указанной блокировкой), записывает в него снова, спит 10 секунд, разблокирует и закрывает его.
- Чтение: открывает целевой файл, читает из него и закрывает его.
- Запись: открывает целевой файл, записывает в него и закрывает его.
Я проверил это, имея две консоли рядом и выполняя следующие действия:
FIRST CONSOLE | SECOND CONSOLE
-----------------------------+-----------------------
php test lock LOCK_SH | php test read
php test lock LOCK_SH | php test write
php test lock LOCK_EX | php test read
php test lock LOCK_EX | php test write
LOCK_SH
кажется, не имеет никакого эффекта, потому что первый процесс, а также второй процесс могут читать и записывать в файл. Если файл заблокирован с LOCK_EX
с помощью первого процесса оба процесса все еще могут писать в него, но только первый процесс может читать. Есть ли какие-либо причины этого?
Вот моя маленькая тестовая программа (протестирована на Windows 7 Home Premium 64-bit):
<?php
// USAGE: php test [lock | read | write] [LOCK_SH | LOCK_EX]
// The first argument specifies whether
// this script should lock the file, read
// from it or write to it.
// The second argument is only used in lock-mode
// and specifies whether LOCK_SH or LOCK_EX
// should be used to lock the file
// Reads $file and logs information.
function r ($file) {
echo "Reading file\n";
if (($buffer = @fread($file, 64)) !== false)
echo "Read ", strlen($buffer), " bytes: ", $buffer, "\n";
else
echo "Could not read file\n";
}
// Sets the cursor to 0.
function resetCursor ($file) {
echo "Resetting cursor\n", @fseek($file, 0, SEEK_SET) === 0 ? "Reset cursor" : "Could not reset cursor", "\n";
}
// Writes $str to $file and logs information.
function w ($file, $str) {
echo "Writing \"", $str, "\"\n";
if (($bytes = @fwrite($file, $str)) !== false)
echo "Wrote ", $bytes, " bytes\n";
else
echo "Could not write to file\n";
}
// "ENTRYPOINT"
if (($file = @fopen("check", "a+")) !== false) {
echo "Opened file\n";
switch ($argv[1]) {
case "lock":
w($file, "1");
echo "Locking file\n";
if (@flock($file, constant($argv[2]))) {
echo "Locked file\n";
w($file, "2");
resetCursor($file);
r($file);
echo "Sleeping 10 seconds\n";
sleep(10);
echo "Woke up\n";
echo "Unlocking file\n", @flock($file, LOCK_UN) ? "Unlocked file" : "Could not unlock file", "\n";
} else {
echo "Could not lock file\n";
}
break;
case "read":
resetCursor($file);
r($file);
break;
case "write":
w($file, "3");
break;
}
echo "Closing file\n", @fclose($file) ? "Closed file" : "Could not close file", "\n";
} else {
echo "Could not open file\n";
}
?>
1 ответ
Это очень хороший вопрос, но также и сложный, потому что он зависит от множества условий.
Мы должны начать с другой пары типов блокировки - рекомендательной и обязательной:
- Консультативная блокировка просто дает вам "флаги состояния", с помощью которых вы знаете, заблокирован ли ресурс или нет.
- Обязательная блокировка обеспечивает блокировку независимо от того, проверяете ли вы эти "флаги состояния".
... и это должно ответить на ваш вопрос, но я продолжу, чтобы объяснить ваш конкретный случай.
Вы, похоже, испытываете поведение консультативных блокировок - ничто не мешает вам читать или записывать в файл, независимо от того, есть ли для него блокировка или вы даже проверили его.
Однако в руководстве по PHP для flock() вы найдете примечание, в котором говорится следующее:
flock() использует обязательную блокировку вместо рекомендательной блокировки в Windows. Обязательная блокировка также поддерживается в операционных системах на базе Linux и System V с помощью обычного механизма, поддерживаемого системным вызовом fcntl(): если в рассматриваемом файле установлен бит разрешения setgid и бит выполнения группы очищен. В Linux файловая система также должна быть смонтирована с опцией mand, чтобы это работало.
Итак, если PHP использует обязательную блокировку в Windows, и вы проверили это в Windows, либо руководство является неправильным / устаревшим / неточным (мне сейчас лень это проверять), либо вам нужно прочитать это большое красное предупреждение на та же страница:
В некоторых операционных системах flock() реализован на уровне процесса. При использовании многопоточного серверного API, такого как ISAPI, вы не сможете полагаться на flock() для защиты файлов от других сценариев PHP, работающих в параллельных потоках одного и того же экземпляра сервера!
flock() не поддерживается в устаревших файловых системах, таких как FAT и его производные, и поэтому всегда будет возвращать FALSE в этой среде (это особенно верно для пользователей Windows 98).
Я не верю, что даже возможно, что ваш исполняемый файл php-cli как-то порождает потоки для себя, так что есть возможность, что вы используете файловую систему, которая просто не поддерживает блокировку.
Я предполагаю, что руководство не совсем точное, и вы фактически получаете консультативные блокировки для Windows, потому что вы также испытываете другое поведение между LOCK_EX
(эксклюзивный замок) и LOCK_SH
(общая блокировка) - для них не имеет смысла различаться, если ваша файловая система просто игнорирует блокировки.
И это подводит нас к разнице между эксклюзивными и общими замками или LOCK_EX
а также LOCK_SH
соответственно. Логика обоих основывается на письме, но есть небольшая разница...
- Исключительные блокировки (как правило) используются, когда вы хотите записать в файл, потому что только один процесс одновременно может удерживать исключительную блокировку для одного и того же ресурса. Это дает вам безопасность, потому что никакой другой процесс не будет читать с этого ресурса, пока владелец блокировки пишет в него.
- Общие блокировки используются, чтобы гарантировать, что ресурс не записывается во время чтения из него. Поскольку ни один процесс не записывает данные в ресурс, он не изменяется и поэтому безопасен для чтения для нескольких процессов одновременно.