为什么在PHP中使用“锁定”文件而不是仅仅计算进程数?

时间:2019-01-18 01:37:39

标签: php command-line-interface daemon

我已经看到很多示例,其中使用“锁定”文件来跟踪当前是否正在运行PHP脚本。

示例:

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

这样,如果长时间运行的脚本启动了两次,则仅第一个实例将运行。哪个很棒。

但是,这似乎是错误的解决方法。为什么我们不只是检查该进程是否已经在运行?

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

此方法是否有潜在的陷阱?为什么我经常看到“锁定”文件的解决方法?用我们正在寻找的名称来计数进程绝对似乎更准确。

4 个答案:

答案 0 :(得分:2)

根据这里的评论和我自己的观察,我组成了两种方法的优缺点列表:

flock方法:

优点:

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

缺点:

  • 不明确。如果您的锁定文件被外部进程/用户删除,则可能会遇到多个进程。如果要将锁定文件保存在/tmp目录中,则这是一种有效的方法,因为该目录中的所有内容都应该是“临时的”
  • 在某些情况下,当进程意外终止时,可以将文件锁转移到不相关的进程(起初我不相信这一点,但是我发现在200多个基于unix的系统中发生(尽管很少)实例,在3种不同的操作系统中)

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

优点:

  • 由于您要实际计数进程,因此无论文件锁的状态如何,它都会每次运行。

缺点:

  • 仅适用于linux
  • 需要启用“ exec”
  • 如果您更改脚本名称,则可能导致双重处理(并确保您的脚本名称未在代码中硬编码)
  • 假设您的脚本只有一个正在运行的“模式”

编辑:我最终使用了这个

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

...因为ps不允许您除了进程名称之外还对进程的所有者进行筛选,并且我希望允许该脚本在其他用户运行时多次运行


编辑2:

...因此,在运行了几天之后,它也不是完美的。不知何故,该过程在同一计算机上在同一用户下多次启动。我唯一的猜测是,某个问题(内存不足等)导致pgrep在应该返回某些内容时什么也不返回。

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

最终,我正在使用另一种解决方案,将当前任务的PID存储在“锁定”文件中,该文件实际上并未被flock锁定。然后,当脚本启动时,检查锁定文件是否存在,如果存在,则获取内容(脚本最后一次启动的PID),然后,通过比较{{1},检查其是否仍在运行。 }内容以及正在运行的脚本的名称。

答案 1 :(得分:0)

首先,命令不正确。当我运行php test.php和命令

 ps -C test.php

一无所获。您可以使用ps -aux|grep 'test.php' -c来获取进程号。但是exec("ps -aux|grep 'php test.php' -c");的返回号必须减去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);
}

答案 2 :(得分:0)

使用“锁定文件”的主要原因仅仅是因为可以预期它们在任何主机环境中都可以工作。如果您可以“创建文件”,并且可以“锁定文件”,则此类代码将在任何地方都可以使用。

还–“没有什么可以通过'聪明'获得的。”众所周知,这种众所周知的策略很好用,因此,“顺其自然。”

答案 3 :(得分:0)

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

锁定文件是原子的

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

文件锁定机制是严格原子的。只有一个进程可以获得文件的排他锁,所以当它有锁时,同一个命令不可能运行两次。

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

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

锁定文件更安全

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

总结

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

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

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

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

分叉子进程的进程确实会“遗赠”它对任何仍在运行的子进程的锁定,但是一旦脚本完成,通过专门解锁文件很容易补救。例如。使用 flock --unlock

长话短说:您应该始终使用锁定文件而不是解析正在运行的进程

其他解决方案

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

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