`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 соответственно. Логика обоих основывается на письме, но есть небольшая разница...

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