在读和写操作上都应该使用LOCK_EX是原子的吗?

27
file_put_contents ( "file", "data", LOCK_EX )

用于写入(意味着 - 获取锁并写入)

file_get_contents ( "file", LOCK_EX )

用于读取(也就是说 - 获取锁然后读取)

它会抛出异常吗?引发错误?阻塞直到获得锁? 或者至少 - 应该吗? PHP有可能某一天会像这样表现吗?

编辑:我知道可以使用rename,但我想知道答案...


当然,你可以使用fopen来获取文件锁,一旦获得了这个锁,你就可以使用file_get_contents函数。因为获取LOCK_SH仅仅阻止其他PHP进程获取LOCK_EX,所以file_get_contents不会返回部分写入/修改的文件(前提是所有的PHP文件访问都严格使用flock)。 - Lee Kowalkowski
2个回答

48
由于这个答案很长,以下是摘要:不,file_get_contents()不是原子的,因为它不遵守建议锁定。 关于PHP中的文件锁:
在*nix平台上,PHP中的文件系统锁仅仅是建议性的。根据文档(重点在于我):
PHP支持以可建议的方式锁定完整的文件(这意味着所有访问程序都必须使用相同的锁定方式,否则它将无法工作)。默认情况下,此函数将阻塞,直到获取所请求的锁定;可以使用下面文档中记录的LOCK_NB选项在非Windows平台上控制此行为。
因此,只要访问该文件的所有进程都使用此锁定方法,那么您就没有问题。
但是,如果您使用一个合理的Web服务器编写静态HTML文件,则该锁将被忽略。在编写过程中,如果有请求进来,Apache将提供部分写入的文件。锁对读取锁的其他进程没有影响。
唯一真正的例外是如果您在文件系统上使用了-o mand的特殊挂载选项来启用强制锁定(但这并不常用,并且可能会有性能损失)。
请阅读文件锁定以获取更多信息。特别是在Unix下的部分。
这意味着合作进程可以使用锁来协调彼此对文件的访问,但程序也可以自由地忽略锁并以任何方式访问文件。因此,总之,使用LOCK_EX将在文件上创建一个咨询锁。只有读者尊重和/或检查锁时,尝试读取文件才会阻塞。如果他们不这样做,锁将被忽略(因为它可以)。试试吧,在一个进程中:
file_put_contents('test.txt', 'Foo bar');
$f = fopen('test.txt', 'a+');
if (flock($f, LOCK_EX)) {
    sleep(10);
    fseek($f, 0);
    var_dump(fgets($f, 4048));
    flock($f, LOCK_UN);
}
fclose($f);

当它处于休眠状态时,请调用此函数:

$f = fopen('test.txt', 'a+');
fwrite($f, 'foobar');
fclose($f);

输出将是foobar...

特别关于file_get_contents:

回答你的另一个具体问题,首先,file_get_contents没有LOCK_EX参数。所以你不能传递它。

现在,看一下源代码,我们可以看到函数file_get_contents在第521行被定义。当你传递file_put_contents('file', 'txt', LOCK_EX);时,与内部函数php_stream_lock相比,没有调用这个函数。

那么,让我们来测试一下吧:

在file1.php文件中:

file_put_contents('test.txt', 'Foo bar');
$f = fopen('test.txt', 'a+');
if (flock($f, LOCK_EX)) {
    sleep(10);
    fseek($f, 0);
    var_dump(fgets($f, 4048));
    flock($f, LOCK_UN);
}
fclose($f);

在file2.php文件中:
var_dump(file_get_contents('test.txt'));

当运行file2.php时,它会立即返回。因此,看起来file_get_contents根本不尊重文件锁定...

1
这种实现方式(使用file_get/put_contents和LOCK_EX)的缓存是否是原子性的?(cache::get(),cache::set())- 前提是不会从其他地方访问这些文件。 - Kamil Tomšík
我意识到在你编辑之前(并添加了使用flock的注释)。 - Kamil Tomšík
@ircmaxell,为什么你在if语句之外使用flock($f, LOCK_UN)?为什么要在第一次没有锁定的情况下解锁呢? - Pacerier
我上面打错了,我是指如何使用 rename() 函数进行锁定? - user4271704
如果file_put_contents不遵守锁定,那么为什么LOCK_EX参数是其第三个参数?难道file_put_contents不遵守锁定不是一个bug吗?@ircmaxell - user4271704
显示剩余2条评论

4

理论问题在程序员上的效果比这里要好得多。

目前,PHP不支持原子文件锁定。

简而言之,PHP不支持合并的fopenflock操作,因此在您的进程可以锁定它之前,另一个进程将始终有一个小的机会来锁定您的进程也已经打开的文件。

话虽如此,flock默认情况下会阻塞,直到锁定被释放。不过请记住关于Linux/BSD上的咨询锁的ircmaxell的注意事项。

注意:对于读取进程,您可能希望将其设置为LOCK_SH,而不是LOCK_EX,以便多个读取线程可以同时锁定它。写入必须始终使用LOCK_EX或者面临数据损坏的风险。

注意2:前面的注释有效是因为只有在没有独占锁存在时才能获取共享锁,但是独占锁需要在获取锁之前不存在任何类型的锁。

@Powerlord,这是否意味着只要我们有持续的读取者(LOCK_SH),LOCK_EX线程就会饿死? - Pacerier
@Pacerier 是的。这是使用 LOCK_SH 的缺点之一。如果你知道会有很多读者不断访问它,就将读取器代码也切换到 LOCK_EX,或者使用专为此类用例编写的数据库。 - Powerlord
@Powerlord,但是数据库是在 LOCK_SH 和 LOCK_EX 这些原语之上实现的。它们采用了哪些方法来确保写操作不会被饿死? - Pacerier
@Pacerier 这取决于数据库引擎。Berkeley DB和SQLite可能会受到影响,但大多数其他数据库(除了MyISAM表上的MySQL)都支持行级锁定。 - Powerlord
1
@Powerlord,关于“不支持组合的fopen和flock操作”,那cc+的fopen标志呢? - Pacerier
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接