`LOCK_EX`禁止阅读,但不写作?

时间:2015-07-31 17:19:46

标签: php flock

为什么我无法阅读使用 LOCK_EX 锁定的文件?我仍然可以写信给它。

我想知道,如果一个进程锁定文件(使用LOCK_SHLOCK_EX)并且另一个进程尝试读取此文件或写入该文件,则会发生什么,但忽略了锁定。所以我制作了一个有三个功能的小脚本:

  • 锁定:打开目标文件,写入目标文件,锁定文件(使用指定锁定),再次写入,休眠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家庭高级版 64位上测试过):

<?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 个答案:

答案 0 :(得分:3)

这是一个非常好的问题,但也很复杂,因为它取决于很多条件。

我们必须从另一对锁定类型开始 - 建议和强制:

  • 咨询锁定只是给你&#34;状态标志&#34;通过它知道资源是否被锁定。
  • 强制锁定强制执行锁定,无论您是否正在检查这些&#34;状态标记&#34;。

......这应该回答你的问题,但我会继续解释你的具体案例。

您似乎遇到的是咨询锁的行为 - 无论是否存在锁定或者您是否检查过文件,都无法阻止您读取或写入文件。<登记/> 但是,您会找到note in the PHP manual for flock(),说明以下内容:

  

flock()在Windows上使用强制锁定而不是建议锁定。通过fcntl()系统调用支持的常用机制,基于Linux和System V的操作系统也支持强制锁定:即,如果相关文件设置了setgid权限位并且组执行位已清除。在Linux上,还需要使用mand选项挂载文件系统,以使其正常工作。

因此,如果PHP在Windows上使用强制锁定并且您在Windows上对此进行了测试,则说明手册错误/过时/不准确(我现在懒得检查)或者你必须在同一页面上阅读这个大红色警告:

  

在某些操作系统上,flock()是在进程级别实现的。使用像ISAPI这样的多线程服务器API时,您可能无法依赖flock()来保护文件免受在同一服务器实例的并行线程中运行的其他PHP脚本的影响!

     

在FAT及其派生类等过时的文件系统上不支持flock(),因此在这种环境下总是会返回FALSE(对于Windows 98用户尤其如此)。

我不相信你的php-cli可执行文件甚至可能以某种方式为自己生成线程,所以你可以选择使用一个根本不存在的文件系统。支持锁定。

我的猜测是手册并不完全准确,事实上你确实在Windows上获得建议锁,因为你在LOCK_EX之间也遇到了不同的行为(独家)锁定)和LOCK_SH(共享锁定) - 如果你的文件系统只是忽略锁定它们就没有意义。

这使我们分别得到独占共享锁或LOCK_EXLOCK_SH之间的区别。两者背后的逻辑基于写作,但有一点点差异......

  • 当您想要写入文件时(通常)使用独占锁,因为一次只有一个进程可以对同一资源保持独占锁。这样可以保证安全,因为当锁定器写入时,没有其他进程会从该资源读取。
  • 共享锁用于确保在您从中读取时未将资源写入。由于没有进程正在写入资源,因此它没有被修改,因此可以安全地同时读取多个进程。