避免flock引起的死锁问题

5

我正在尝试模拟在繁忙站点上进行文件写入。我编写了以下代码,但最终导致计算机冻结。

$loop = 10000;
$sleep = 500000;
$i =0;

while($i < $loop) {

    $mtime = microtime();
    $mtime = explode(" ",$mtime);
    $mtime = $mtime[1] + $mtime[0];
    $starttime = $mtime; 

    $handler = fopen($file,"a+");
    if($handler) {
    if (flock($handler, LOCK_EX)) {
        $mtime = microtime();
        $mtime = explode(" ",$mtime);
        $mtime = $mtime[1] + $mtime[0];
        $endtime = $mtime;
        $totaltime = ($endtime - $starttime); 

        fwrite($handler,"Script 1 took $totaltime secs\n");
    }

    flock($handler, LOCK_UN);
    fclose($handler);
}
$i++;
usleep($sleep);
}

我不能使用LOCK_NB,因为它在Windows上无法工作。如果执行以上代码的进程少于13个,则该代码可以正常工作。如何处理这种死锁情况?


2
几个PHP进程之间的死锁不应该冻结整个系统。您确定这是死锁吗? - Matti Virkkunen
1
LOCK_NB似乎在Windows上运行正常,尽管文档中说的不一样:http://bugs.php.net/bug.php?id=54129 - magma
1
我同意,应该避免死锁,但有些进程需要等待。如果您的系统冻结了,可能是其他原因造成的。为什么要将数据附加到文件中,而不使用数据库呢? - Erik
1
如果您执行$starttime = microtime(TRUE),您将获得时间值作为简单的浮点数,省去了 explode/addition 步骤。 - Marc B
Jason,你最终找出了导致死锁的原因吗? - Peter
显示剩余2条评论
4个回答

2
阅读您的代码,我认为您应该将 flock($handler, LOCK_UN); 移到 if (flock($handler, LOCK_EX)) {} 的条件块内。
为了准确找出问题所在,我建议在每次调用 flock()fopen()fwrite()fclose() 之前和之后添加带有日期时间戳的(并且刷新,以便不会被卡在输出缓冲区中)调试输出,并将每个脚本实例的输出重定向到其自己的文件中。
然后,在冻结和重新启动之后,您可以查看每个文件的末尾,看看重新启动时每个脚本正在执行什么操作。通过比较日期时间戳,您应该能够看到哪些脚本先“冻结”。

LOCK_UN 移入 if 中可以避免死锁。如果在外面,进程可能会浪费 LOCK_UN 用于不存在的锁,从而导致阻塞进程。 - Markus Malkusch

1

尝试使用file_put_contents():

<?php

$file ='file.txt';

$str ="一些文本\n";

file_put_contents($file, $str, FILE_APPEND | LOCK_EX);

?>


1
在编写更多的 PHP 代码之前,我建议您使用随 Apache 一起安装的应用程序 AB(Apache Benchmark)来模拟本地主机中的高负载。例如:

ab -n 1000 -c 200 http://localhost/your.php

通过这种方式,您可以模拟 200 个并发用户和 1000 个请求。


0
尝试使用额外的mkdir()锁定机制来“保护”LOCK_EX,如此处所述: https://dev59.com/1mw05IYBdhLWcg3w41sp#7927642 示例:
<?php
$file = 'deadlock.txt';
$loop = 10000;
$sleep = 500000;
$i = 0;
while ($i < $loop) {
    $starttime = microtime(true);
    $handler = fopen($file, 'a+');
    if ($handler) {
        if (!file_exists($file . '_lock')) {
            if (mkdir($file . '_lock')) {
                if (flock($handler, LOCK_EX)) {
                    $endtime = microtime(true);
                    $totaltime = ($endtime - $starttime);
                    $totaltime = number_format($endtime - $starttime, 10);
                    if ($totaltime > 1) {
                        break;
                    }
                }
                flock($handler, LOCK_UN);
                fclose($handler);
                rmdir($file . '_lock');
            }
        }
    }
    $i++;
    usleep($sleep);
}
?>

正如您所看到的,我添加了一个break;以避免死锁。如果脚本停止,您可以查看日志文件。

在Linux中,我使用此mkdir()技巧而不使用flock(),因为它是原子性的。我不知道这是否也适用于Windows。


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