为什么在 PHP 中使用 "lock" 个文件而不是只计算进程数?

Why are "lock" files used in PHP instead of just counting the processes?

我见过很多示例,其中 "lock" 文件用于跟踪 PHP 脚本当前是否正在 运行ning。

示例:

  1. 脚本开始
  2. 检查“/tmp/lockfile”当前是否被锁定
  3. 如果被锁定,退出。如果没有,锁定文件并继续

这样,如果长时间运行ning 脚本启动两次,只有第一个实例会运行。太棒了。

但是,绕过它似乎是错误的方法。为什么我们不检查进程是否已经像这样 运行ning?

if(exec("ps -C " . basename(__FILE__) . " --no-headers | wc -l") > 1){
  echo "Already running.";
  exit;
}

这种方法有什么潜在的缺陷吗?为什么我经常看到 "lock" 文件解决方法?用我们要查找的名称来计算进程显然更准确....

第一,命令不对。当我 运行 php test.php 和命令

 ps -C test.php

什么也得不到。可以用ps -aux|grep 'test.php' -c得到进程号number.Butexec("ps -aux|grep 'php test.php' -c");return号必须减去2才是真正的进程号

使用锁定文件的主要原因是 exec 或其他命令功能需要一些特殊权限,并且在 php.ini 默认配置中是 disable_functions。

像这样测试脚本:

$count = exec("ps -aux|grep 'php test.php' -c");

if($count > 3){
  echo "Already running.".$count;
  exit;
}

while(1){
    sleep(20);
}

使用 "lock files" 的主要原因仅仅是因为它们可以在 任何 主机环境中工作。如果你可以 "create a file," 并且如果你可以 "lock it," 这样的代码将在任何地方工作......

另外 – "there is nothing to be gained by being 'clever.'" 这个众所周知的策略运作良好 – 因此,"run with it."

根据此处的评论和我自己的观察,我列出了两种方法的优缺点:

flock方法:

优点:

  • 跨操作系统更兼容
  • 不需要 bash 知识
  • 更常见的方法,很多例子
  • 即使禁用 exec() 也能工作
  • 可以在单个文件中使用多个锁以允许同时对同一文件进行不同的 运行ning "modes"

缺点:

  • 不确定。如果您的锁定文件被外部进程/用户删除,您最终可能会遇到多个进程。如果您将锁定文件保存在 /tmp 目录中,这是一种有效的可能性,因为该目录中的所有内容都应该是 "temporary"
  • 在某些情况下,当一个进程意外死亡时,文件锁可以被运行转移给一个不相关的进程(起初我不相信,但我发现它发生的实例(虽然很少)跨越 200 多个基于 unix 的系统,在 3 个不同的操作系统中)

exec("ps -C...") 方法

优点:

  • 因为你实际计算进程,它每次都会工作,不管文件锁的状态等

缺点:

  • 仅适用于 linux
  • 需要 "exec" 启用
  • 如果您更改脚本的名称,可能会导致双重进程(并确保您的脚本名称未硬编码在代码中)
  • 假设你的脚本只有一个 运行ning "mode"

编辑:我最终使用了这个:

if (exec("pgrep -x " . $scriptName . " -u ". $currentUser . " | wc -l") > 1)
{
  echo $scriptName . " is already running.\n";
  exit;
}

... 因为 ps 除了进程名称之外不允许您过滤进程的所有者,我希望允许此脚本多次 运行 如果另一个用户 运行正在使用它。


编辑 2:

...所以,在 运行ning 几天后,它也不完美。不知何故,该进程在同一用户下的同一台机器上多次启动。我唯一的猜测是有一些问题(运行 内存不足等)导致 pgrep 到 return 什么都没有,而它应该 return 编辑了一些东西。

所以这意味着 flock 方法和计数过程方法都不是 100% 可靠的。您必须确定哪种方法更适合您的项目。

最终,我使用了另一种解决方案,将当前任务的 PID 存储在 "lock" 文件中,实际上并没有被 flock 锁定。然后,当脚本启动时,检查锁定文件是否存在,如果存在,则获取内容(脚本上次启动的PID) 然后,它检查它是否仍然是运行ning,通过比较/proc/#PID#/cmdline 内容与脚本的名称 运行ning.

使用锁定文件的最重要原因(不幸的是其他答案中没有给出)是锁定文件使用一种原子锁定机制,允许您运行 您的脚本的多个实例,在比脚本本身更高级别的上下文中,并且更安全

锁定文件是原子的

遍历进程列表本质上容易出现竞争条件;在检索和遍历列表所需的时间中,第二个进程可能刚刚产生,而您无意中结束了多个进程。

文件锁定机制是严格原子的。只有一个进程可以获得文件的独占锁,因此当它确实有锁时,同一个命令不可能 运行ning 两次。

锁定文件允许同一脚本的多个实例

假设您想要一个脚本的多个独立实例 运行ning,但每个实例都有自己的 scope/options。如果您只是计算脚本在 ps 输出中出现的次数,您将只能 运行 一个实例。通过使用单独的锁定文件,您可以在单个范围内正确锁定其 运行ning 状态。

锁定文件更安全

锁文件是一种更加优雅和安全的设计。与其滥用进程列表(这需要更多权限并且必须在每个 OS 上以不同方式访问和解析)来推断脚本是否已经 运行ning,您有一个专用系统明确锁定状态。

总结

因此重申锁定文件优于其他方法的所有原因:

  • 锁定文件允许原子锁定,否定竞争条件。
  • 它们允许同一脚本的 运行宁 多个实例/范围。
  • 文件系统访问比执行权限或对进程信息的完全访问更普遍可用
  • 它们更安全,因为不需要特定锁定文件之外的权限。
  • 使用文件锁在不同操作系统之间更兼容
  • 这是一个更优雅的解决方案;不需要通过进程列表进行解析。

至于其他答案中提到的缺点;可以删除锁文件,有时锁会转移到另一个进程:

通过设置适当的权限并将文件存储在非易失性存储器中,可以防止删除锁定文件。如果根据规范使用,锁定文件是“确定的”,这与进程列表不同。

派生子进程的进程确实会“遗留”它锁定到仍在 运行ning 的任何子进程,但是一旦脚本完成,通过专门解锁文件很容易解决这个问题。例如。使用 flock --unlock.

长话短说:您应该始终在解析 运行宁进程.

时使用锁定文件

其他解决方案

还有其他解决方案,但除非您有其他要求,否则它们通常不会比简单的文件锁定有任何优势:

  • 单独数据库/存储(例如 redis)中的互斥量。
  • 专门侦听网络接口上的端口。