确保Linux上只有一个正在运行的PHP进程

3

我有一个php脚本每分钟从cron运行一次。但有时它的运行时间超过1分钟。

我的问题是:保证现在只有一个进程正在运行的最佳方法是什么?

我使用了这段代码:

$output = shell_exec('ps aux | grep some_script.php | grep -v grep');   //get all processes containing "some_script.php" and exclude current grep process
$trimmed = rtrim($output, PHP_EOL); //trim newline symbol in the end
$processes = explode(PHP_EOL, $trimmed);    //get the array of lines (i.e. processes)
$procCnt = count($processes);   //get number of lines
if ($procCnt > 2) {
  echo "busy\n";
  exit();       //exit if number of processes more than 2 (see explaination below)
}

如果有一个"some_script"进程从cron中运行,shell_exec返回类似于以下的结果:
apache     13593  0.0  0.0   9228  1068 ?        Ss   18:20   0:00 /bin/bash -c php -f /srv/www/robot/some_script.php 2>&1
apache     13602  0.0  0.0 290640 10544 ?        S    18:20   0:00 php -f /srv/www/robot/some_script.php

如果输出中有两行以上,我就会调用exit()函数。

我想问:我这样做对吗?还是有更好的方法?

非常感谢您的帮助。

3个回答

5

检查这种方式会创建竞态条件,两个进程可以同时检索列表,然后都决定退出。根据你想要做什么,这可能是一个问题或不是一个问题。

可能更好的选择是创建某种锁定。我使用过的一个简单方法是创建一个只在进程运行时存在的目录-mkdir是原子性的,它将成功(没有其他进程正在运行)或失败(另一个进程已经创建了它)。完成后一定要记得将其删除:

if (!mkdir("lock_dir")) {
   echo "busy\n";
   exit();
}
register_shutdown_function(function() {
   rmdir("lock_dir");
});

或者更好的是,看起来flock是为类似的目的而设计的。这是手册中的示例:
<?php

$fp = fopen("/tmp/lock.txt", "r+");

if (flock($fp, LOCK_EX)) {  // acquire an exclusive lock
    ftruncate($fp, 0);      // truncate file
    fwrite($fp, "Write something here\n");
    fflush($fp);            // flush output before releasing the lock
    flock($fp, LOCK_UN);    // release the lock
} else {
    echo "Couldn't get the lock!";
}

fclose($fp);

?>

只需在脚本运行时保持锁定,类似于我的第一个示例。


我应该补充说明,“据我所知”,PHP的mkdir是原子性的。如果我错了,那么使用flock和shutdown函数可能会更好。 - Izkata
我不知道register_shutdown_function。非常感谢!它解决了问题。 - Oleg
1
register_shutdown_function 可能在某些类型的错误上不会被调用。为了确保其正常工作,我建议为锁添加“过期时间”。检查文件夹的 filemtime,如果超过 15 分钟,则删除该锁并继续执行 cron(您也可以使用脚本杀死所有 PHP 进程)。 - Tsanyo Tsanev

1

最简单的方法是在进程启动时创建文件,在最后删除它。如果文件存在,则继续创建它,否则退出。

另一种方法是将Apache的MaxClients限制为一个(如果在您的情况下是有效选项)。


谢谢您的快速回复!我考虑过这种方法,但是如果脚本出现致命错误(没有人是完美的),它将被终止,文件也不会被删除。 - Oleg
@Izkata的回答有一个解决方案 - register_shutdown_function - Oleg
@ZUb 如果文件存在,则检查其创建时间(使用stat函数)- 如果比几分钟前旧,则删除并按照通常方式继续。 - StormRideR

0

为了实现面向对象编程风格,我使用独立的Helper类。

flock — 可移植的文件锁定

register_shutdown_function — 注册一个函数,在关闭时执行

<?php

class ProcessHelper
{
    const PIDFILE = 'yourProcessName.pid';

    public static function isLocked()
    {
        $fp = fopen(self::PIDFILE, 'w');

        if (!flock($fp, LOCK_EX | LOCK_NB, $wouldBlock)) {
            if ($wouldBlock) {
                // if this file locked by other process
                var_dump('DO NOTHING');
                return true;
            }
        } else {
            var_dump('Do something and remove PID file');
            register_shutdown_function(function () {
                unlink(self::PIDFILE);
            });
            sleep(5); //for test, uncomment
        }
        return false;
    }
}


class FooService
{
    public function init()
    {
        if (!ProcessHelper::isLocked()) {
            var_dump('count 1000\n');
            for ($i = 0; $i < 10000; $i++) {
                echo $i;
            }
        }
    }
}

$foo = new FooService();
$foo->init();

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