【问题标题】:`LOCK_EX` prohibits reading, but not writing?`LOCK_EX`禁止读,但不写?
【发布时间】:2015-10-23 11:06:29
【问题描述】:

为什么我无法读取被LOCK_EX锁定的文件?我仍然可以写入。

我想知道,如果一个进程锁定了一个文件(使用LOCK_SHLOCK_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 位上测试):

<?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";
    }
?>

【问题讨论】:

    标签: php flock


    【解决方案1】:

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

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

    • 咨询锁定只是为您提供“状态标志”,通过这些标志可以知道资源是否被锁定。
    • 强制锁定强制锁定,无论您是否检查这些“状态标志”。

    ...这应该可以回答您的问题,但我会继续解释您的具体情况。

    您似乎遇到的是建议锁的行为 - 没有什么能阻止您读取或写入文件,无论文件是否有锁,或者您是否检查过。
    但是,你会找到一个note in the PHP manual for flock(),上面写着:

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

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

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

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

    我认为您的 php-cli 可执行文件甚至不可能以某种方式为自己生成线程,因此您可以选择使用根本不支持锁定的文件系统。

    我的猜测是手册并不完全准确,实际上您确实在 Windows 上获得了 advisory 锁,因为您在 LOCK_EX(独占锁)和 @ 之间也遇到了不同的行为987654323@(共享锁) - 如果您的文件系统只是忽略锁,那么它们的区别是没有意义的。

    这将我们带到独占共享锁或LOCK_EXLOCK_SH之间的区别。两者背后的逻辑都是基于写作,但有细微的差别......

    • (通常)在您要写入文件时使用排他锁,因为一次只有一个进程可能对同一资源持有排他锁。这为您提供了安全性,因为当锁持有者写入该资源时,没有其他进程会从该资源中读取。
    • 共享锁用于确保在读取资源时不会将资源写入。由于没有进程写入资源,因此它没有被修改,因此可以安全地同时读取多个进程。

    【讨论】:

    • 我尝试检查 PHP 的源代码,但在某个时候调用了函数指针,我放弃了。 NTFS 支持文件锁定,并且本机功能 LockFileEx 按照您的描述工作,所以我认为它是在进程级别实现的。 (以及 +1 以获得详细答案:)
    • @Narf,当“你可能无法依赖flock”时,flock返回true还是false?那么如何检测那个呢?
    • @Pacerier 不知道;可能true - 该注释是关于多线程的,其中两个单独的进程线程尝试获取同一资源上的锁。由于锁定发生在进程级别,同一进程 ID 下的两个线程可能都成功。在这些条件下查看返回值是没有意义的 - 如果您有这样的环境,您只需找到不同的解决方案。
    猜你喜欢
    • 1970-01-01
    • 2021-05-17
    • 1970-01-01
    • 1970-01-01
    • 2020-06-27
    • 2018-09-11
    • 1970-01-01
    • 2020-08-19
    • 1970-01-01
    相关资源
    最近更新 更多