我在输入问题时意识到了这个问题的答案,所以我也会发布我的答案。当另一个阅读器脚本试图读取文件时,在截断文件后获取文件的建议锁定可能会成为问题。'w'
如果阅读器脚本碰巧在编写器脚本以模式打开文件的时间和它获取文件锁定的时间之间读取文件,则阅读器脚本将遇到截断的文件(空文件) 。
这是演示该问题的两个脚本。第一个脚本将其 PID 写入名为 foo.txt 的文件中。第二个脚本尝试从此文件中读取 PID。
写.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);
?>
读取.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);
?>
以下 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
出现此问题的原因是我们在文件被截断后获得了锁定。这有点太晚了。我们希望首先获得一个锁,然后对其执行截断或任何其他修改。有两种方法可以做到这一点。
使用特殊的锁定文件
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);
?>
以下 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
以模式打开文件'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);
?>
该脚本利用了在'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