`LOCK_EX` 禁止读,但不写?

Cub*_*i73 2 php flock

为什么我无法读取用 锁定的文件 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
Run Code Online (Sandbox Code Playgroud)

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";
    }
?>
Run Code Online (Sandbox Code Playgroud)

Nar*_*arf 5

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

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

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

...这应该可以回答你的问题,但我会继续解释你的特殊情况。

您似乎正在经历的是咨询锁的行为 - 没有什么可以阻止您读取或写入文件,无论是否有锁,或者您是否检查过。
但是,您会在 PHP 手册中找到 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 上获得了咨询锁,因为您在LOCK_EX(排他锁)和LOCK_SH(共享锁)之间也遇到了不同的行为- 它们没有意义如果您的文件系统只是忽略锁,则不同。

这给我们带来了排他锁和共享锁之间的区别,或者分别是LOCK_EXLOCK_SH。两者背后的逻辑都基于write,但略有不同......

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