什么时候PHP flock需要一个特殊的锁文件或'c'模式的打开文件?

Sus*_*Pal 0 php locking file

引用PHP flock文档:

因为flock()需要一个文件指针,你可能必须使用一个特殊的锁文件来保护对你想要截断的文件的访问,方法是在写模式下打开它(fopen()带有"w"或"w +"参数).

引用PHP fopen文档:

'c' - 打开文件只写.如果该文件不存在,则创建该文件.如果存在,则既不截断(与'w'相反),也不会调用此函数失败(如'x'的情况).文件指针位于文件的开头.如果在尝试修改文件之前需要获取咨询锁(请参阅flock()),这可能很有用,因为使用'w'可能会在获取锁之前截断文件(如果需要截断,则ftruncate()可以在请求锁之后使用).

我试图flock通过fopen'w'模式下打开文件来解决文件被截断后获取咨询锁的问题.在什么情况下,人们会想要使用一个特殊的锁文件来保护对一个打算在写入模式下打开它的文件的访问?

Sus*_*Pal 5

在输入问题时我意识到了这个问题的答案,所以我也会发布我的答案.在截断文件后获取文件的建议锁定可能是另一个读取器脚本尝试读取文件时的问题.如果读取器脚本恰好在写入器脚本以'w'模式打开文件的时间与获取文件锁定的时间之间读取文件,则会遇到截断的文件(空文件).

以下是演示此问题的两个脚本.第一个脚本将其PID写入名为foo.txt的文件中.第二个脚本尝试从此文件中读取PID.

write.php:

<?php
$f = fopen('foo.txt', 'w');

sleep(5); // Artificial delay between open and lock

flock($f, LOCK_EX);
fwrite($f, getmypid() . "\n");
flock($f, LOCK_UN);
fclose($f);
?>
Run Code Online (Sandbox Code Playgroud)

read.php:

<?php
$f = fopen('foo.txt', 'r');
flock($f, LOCK_EX);

$size = filesize('foo.txt');
echo ($size === 0 ? "File is empty\n" : fread($f, $size));

flock($f, LOCK_UN);
fclose($f);
?>
Run Code Online (Sandbox Code Playgroud)

以下shell会话显示read.php在write.php打开文件之后和write.php获取文件锁之前尝试读取文件时发现一个空文件.

$ php write.php < /dev/null &
[1] 17511
$ for i in {1..10}; do php read.php; sleep 1; done
File is empty
File is empty
File is empty
File is empty
File is empty
[1]+  Done                    php write.php < /dev/null
17511
17511
17511
17511
17511
Run Code Online (Sandbox Code Playgroud)

出现此问题的原因是我们在文件被截断后获取锁定.那有点太晚了.我们想先获取一个锁,然后对其执行截断或任何其他修改.有两种方法可以做到这一点.

使用特殊的锁文件

write2.php:

<?php
$lock = fopen('foo.lock', 'w');

sleep(5); // Artificial delay between open and lock

flock($lock, LOCK_EX);
$f = fopen('foo.txt', 'w');
fwrite($f, getmypid() . "\n");
fclose($f);
flock($lock, LOCK_UN);
?>
Run Code Online (Sandbox Code Playgroud)

以下shell会话显示read.php从未遇到过截断文件.

$ php write2.php < /dev/null &
[1] 17533
$ for i in {1..10}; do php read.php; sleep 1; done
17511
17511
17511
17511
17511
[1]+  Done                    php write2.php < /dev/null
17533
17533
17533
17533
17533
Run Code Online (Sandbox Code Playgroud)

'c'模式下打开文件然后锁定它

write3.php:

<?php
$f = fopen('foo.txt', 'c');

sleep(5); // Artificial delay between open and lock

flock($f, LOCK_EX);
ftruncate($f, 0);
fwrite($f, getmypid() . "\n");
flock($f, LOCK_UN);

fclose($f);
?>
Run Code Online (Sandbox Code Playgroud)

这个脚本利用了这样一个事实,即在'c'模式下打开文件不会自动截断文件,所以现在我们可以ftruncate在获取锁定之后和写入之前截断文件.因此,read.php永远不会遇到截断的文件.

$ php write3.php < /dev/null &
[1] 17558
$ for i in {1..10}; do php read.php; sleep 1; done
17533
17533
17533
17533
17533
[1]+  Done                    php write3.php < /dev/null
17558
17558
17558
17558
17558
Run Code Online (Sandbox Code Playgroud)